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

diff --git a/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsHaocaiServiceImpl.java b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsHaocaiServiceImpl.java
new file mode 100644
index 0000000..146d5aa
--- /dev/null
+++ b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsHaocaiServiceImpl.java
@@ -0,0 +1,1266 @@
+package jnpf.limsService.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+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.LimsHaocaiChukuEntity;
+import jnpf.limsEntity.LimsHaocaiChukuShenqingEntity;
+import jnpf.limsEntity.LimsHaocaiChukuShenqingZixiangEntity;
+import jnpf.limsEntity.LimsHaocaiChukuZixiangEntity;
+import jnpf.limsEntity.LimsHaocaiJiechuEntity;
+import jnpf.limsEntity.LimsHaocaiLogEntity;
+import jnpf.limsEntity.LimsHaocaiRukuEntity;
+import jnpf.limsEntity.LimsHaocaiRukuSubmitResult;
+import jnpf.limsEntity.LimsHaocaiRukuYanshouEntity;
+import jnpf.limsEntity.LimsHaocaiRukuZixiangEntity;
+import jnpf.limsEntity.LimsHaocaiXiaohuiShenqingEntity;
+import jnpf.limsEntity.LimsHaocaiXiaohuiShenqingZixiangEntity;
+import jnpf.limsEntity.LimsHaocaiYanshouShenqingEntity;
+import jnpf.limsMapper.LimsHaocaiChukuShenqingMapper;
+import jnpf.limsMapper.LimsHaocaiChukuShenqingZixiangMapper;
+import jnpf.limsMapper.LimsHaocaiChukuZixiangMapper;
+import jnpf.limsMapper.LimsHaocaiJiechuMapper;
+import jnpf.limsMapper.LimsHaocaiLogMapper;
+import jnpf.limsMapper.LimsHaocaiRukuMapper;
+import jnpf.limsMapper.LimsHaocaiRukuYanshouMapper;
+import jnpf.limsMapper.LimsHaocaiRukuZixiangMapper;
+import jnpf.limsMapper.LimsHaocaiXiaohuiShenqingMapper;
+import jnpf.limsMapper.LimsHaocaiXiaohuiShenqingZixiangMapper;
+import jnpf.limsMapper.LimsHaocaiYanshouShenqingMapper;
+import jnpf.limsService.LimsHaocaiService;
+import jnpf.util.RandomUtil;
+import jnpf.util.StringUtil;
+import jnpf.util.UserProvider;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鑰楁潗涓氬姟鏈嶅姟瀹炵幇銆傚瓙琛� lims_haocai_ruku_zixiang 涓嶅崟璁� service锛�
+ * 缁� Mapper 鐩存帴璇诲啓锛堝钩鍙� ProductServiceImpl 鍚屾妯″紡锛夈��
+ */
+@Slf4j
+@Service
+public class LimsHaocaiServiceImpl
+        extends SuperServiceImpl<LimsHaocaiRukuMapper, LimsHaocaiRukuEntity>
+        implements LimsHaocaiService {
+
+    /** 鏉$爜缂栫爜鐨勫崟鎹鍒� enCode锛坆ase_bill_rule锛孒C_ + YYYYMMDD + 6浣嶆祦姘达級 */
+    private static final String TIAOMA_BILL_RULE = "haocai_ruku_tiaoma";
+
+    /** 瀛愯〃鍒濆鐘舵�侊細鏈嚭搴� */
+    private static final String BIZ_STATUS_WEICHUKU = "weichuku";
+
+    /** 瀛愯〃鐘舵�侊細宸插嚭搴� */
+    private static final String BIZ_STATUS_YICHUKU = "yichuku";
+
+    /** 瀛愯〃鐘舵�侊細宸插紑鐡� */
+    private static final String BIZ_STATUS_YIKAIPING = "yikaiping";
+
+    /** 瀛愯〃鐘舵�侊細宸查攢姣� */
+    private static final String BIZ_STATUS_YIXIAOHUI = "yixiaohui";
+
+    /** 瀛愯〃鐘舵�侊細寰呴攢姣� */
+    private static final String BIZ_STATUS_DAIXIAOHUI = "daixiaohui";
+
+    /** 鐢抽娴佺▼鐘舵�侊細宸查�氳繃 */
+    private static final int FLOW_STATE_APPROVED = 2;
+
+    /** 鍊熷嚭鍗曠姸鎬侊細宸插�熷嚭 */
+    private static final String JIECHU_STATUS_LENT = "lent";
+
+    /** 鍊熷嚭鍗曠姸鎬侊細宸插綊杩� */
+    private static final String JIECHU_STATUS_RETURNED = "returned";
+
+    /** 鑰楁潗鏃ュ織鎿嶄綔绫诲瀷锛氬叆搴� */
+    private static final String LOG_ACTION_RUKU = "鑰楁潗鍏ュ簱";
+
+    /** 鑰楁潗鏃ュ織鎿嶄綔绫诲瀷锛氬�熷嚭 */
+    private static final String LOG_ACTION_JIECHU = "鑰楁潗鍊熷嚭";
+
+    /** 鑰楁潗鏃ュ織鎿嶄綔绫诲瀷锛氬綊杩� */
+    private static final String LOG_ACTION_GUIHUAN = "鑰楁潗褰掕繕";
+
+    /** 鑰楁潗鏃ュ織鎿嶄綔绫诲瀷锛氬嚭搴� */
+    private static final String LOG_ACTION_CHUKU = "鑰楁潗鍑哄簱";
+
+    /** 鑰楁潗鏃ュ織鎿嶄綔绫诲瀷锛氶攢姣� */
+    private static final String LOG_ACTION_XIAOHUI = "鑰楁潗閿�姣�";
+
+    /** 楠屾敹鐘舵�侊細宸查獙鏀� */
+    private static final String YANSHOU_STATUS_YIYANSHOU = "yiyanshou";
+
+    /** 楠屾敹鐘舵�侊細涓嶉�傜敤 */
+    private static final String YANSHOU_STATUS_NA = "na";
+
+    /** 鑰楁潗楠屾敹鐢宠鐘舵�侊細寰呴獙鏀� */
+    private static final String YANSHOU_SHENQING_STATUS_DAIYANSHOU = "daiyanshou";
+
+    /** 鑰楁潗楠屾敹鐢宠鐘舵�侊細楠屾敹涓� */
+    private static final String YANSHOU_SHENQING_STATUS_YANSHOUZHONG = "yanshouzhong";
+
+    /** 鑰楁潗楠屾敹鐢宠鐘舵�侊細宸查獙鏀� */
+    private static final String YANSHOU_SHENQING_STATUS_YIYANSHOU = "yiyanshou";
+
+    @Autowired
+    private BillRuleApi billRuleApi;
+
+    @Autowired
+    private LimsHaocaiRukuZixiangMapper zixiangMapper;
+
+    @Autowired
+    private LimsHaocaiJiechuMapper jiechuMapper;
+
+    @Autowired
+    private LimsHaocaiChukuShenqingMapper chukuShenqingMapper;
+
+    @Autowired
+    private LimsHaocaiChukuShenqingZixiangMapper chukuShenqingZixiangMapper;
+
+    @Autowired
+    private LimsHaocaiChukuZixiangMapper chukuZixiangMapper;
+
+    @Autowired
+    private LimsHaocaiLogMapper haocaiLogMapper;
+
+    @Autowired
+    private LimsHaocaiXiaohuiShenqingMapper xiaohuiShenqingMapper;
+
+    @Autowired
+    private LimsHaocaiXiaohuiShenqingZixiangMapper xiaohuiShenqingZixiangMapper;
+
+    @Autowired
+    private LimsHaocaiYanshouShenqingMapper yanshouShenqingMapper;
+
+    @Autowired
+    private LimsHaocaiRukuYanshouMapper rukuYanshouMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "RUKU_SUBMIT", label = "鑰楁潗鍏ュ簱鎻愪氦", bizType = "HAOCAI",
+            bizCodeExpr = "", bizCodeRequired = false, targetTable = "lims_haocai_ruku",
+            targetIdExpr = "#ruku.id", diff = true, entityClass = LimsHaocaiRukuEntity.class)
+    public LimsHaocaiRukuSubmitResult submitRuku(LimsHaocaiRukuEntity ruku) throws DataException {
+        BigDecimal shuliang = parseShuliang(ruku.getRukuShuliang());
+        BigDecimal rongliang = parseRongliang(ruku.getRongliang());
+        String yanshouZhuangtai = resolveYanshouZhuangtai(ruku);
+        ruku.setYanshouZhuangtai(yanshouZhuangtai);
+        ruku.setBizStatus(BIZ_STATUS_WEICHUKU);
+        if (!this.updateById(ruku)) {
+            throw new DataException("鍏ュ簱鍗曠姸鎬佹洿鏂板け璐ワ細" + ruku.getId());
+        }
+        if (isYanshouEnabled(ruku)) {
+            updateRukuYanshouRukuId(ruku);
+        }
+
+        boolean yigeTiaoma = "yes".equalsIgnoreCase(trimToEmpty(ruku.getShifouYigeTiaoma()));
+        int completeCount = calculateCompleteTiaomaCount(shuliang, ruku.getRukuShuliang());
+        BigDecimal fractionalQuantity = shuliang.remainder(BigDecimal.ONE);
+        int count = yigeTiaoma ? 1 : completeCount + (fractionalQuantity.signum() > 0 ? 1 : 0);
+        // 涓�涓潯鐮侊細鎬婚噺 = 鍏ュ簱鏁伴噺 脳 瀹归噺锛涘涓潯鐮侊細鏁存暟閮ㄥ垎姣忔潯涓哄閲忥紝灏忔暟閮ㄥ垎鎸夋瘮渚嬬敓鎴愭渶鍚庝竴鏉°��
+        BigDecimal singleTiaomaZongliang = rongliang.multiply(shuliang);
+
+        // 缂栧彿 = 鎵瑰彿 + "-" + 搴忓彿锛屽簭鍙锋寜鏈壒鎬绘潯鏁板姩鎬佽ˉ闆讹紙3鈫�1浣嶃��20鈫�2浣嶃��100鈫�3浣嶏級锛�
+        // 鏁伴噺澶氬ぇ閮借嚜閫傚簲锛涙壒鍙蜂负绌烘椂鍥為��鐢ㄤ富琛� id锛岄伩鍏嶇紪鍙风己鍓嶇紑
+        String bianhaoPrefix = trimToEmpty(ruku.getPihao());
+        if (bianhaoPrefix.isEmpty()) {
+            bianhaoPrefix = ruku.getId();
+        }
+        int width = String.valueOf(count).length();
+        List<String> tiaomaBianmaList = new ArrayList<>(count);
+        List<String> bianhaoList = new ArrayList<>(count);
+        for (int i = 1; i <= count; i++) {
+            String tiaomaBianma = nextTiaomaBianma();
+            String bianhao = bianhaoPrefix + "-" + String.format("%0" + width + "d", i);
+            BigDecimal zongliang = yigeTiaoma
+                    ? singleTiaomaZongliang
+                    : (i > completeCount ? rongliang.multiply(fractionalQuantity) : rongliang);
+            tiaomaBianmaList.add(tiaomaBianma);
+            bianhaoList.add(bianhao);
+            LimsHaocaiRukuZixiangEntity zixiang =
+                    buildZixiang(ruku, tiaomaBianma, bianhao, toPlainString(zongliang), yanshouZhuangtai);
+            zixiangMapper.insert(zixiang);
+            haocaiLogMapper.insert(buildRukuLog(ruku, zixiang));
+        }
+
+        log.info("鑰楁潗鍏ュ簱鎻愪氦瀹屾垚锛歳ukuId={}, tiaomaList={}, count={}, rukuShuliang={}",
+                ruku.getId(), tiaomaBianmaList, count, ruku.getRukuShuliang());
+
+        LimsHaocaiRukuSubmitResult result = new LimsHaocaiRukuSubmitResult();
+        result.setTiaomaBianma(tiaomaBianmaList.isEmpty() ? null : tiaomaBianmaList.get(0));
+        result.setTiaomaBianmaList(tiaomaBianmaList);
+        result.setBianhaoList(bianhaoList);
+        result.setCount(count);
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "JIECHU_SUBMIT", label = "鑰楁潗鍊熷嚭鎻愪氦", bizType = "HAOCAI",
+            bizCodeExpr = "", bizCodeRequired = false, targetTable = "lims_haocai_jiechu",
+            targetIdExpr = "#jiechu.id", diff = true, entityClass = LimsHaocaiJiechuEntity.class)
+    public void submitJiechu(LimsHaocaiJiechuEntity jiechu) throws DataException {
+        String tiaomaBianma = trimToEmpty(jiechu == null ? null : jiechu.getTiaomaBianma());
+        if (tiaomaBianma.isEmpty()) {
+            throw new DataException("鏉$爜缂栫爜涓嶈兘涓虹┖");
+        }
+
+        List<LimsHaocaiRukuZixiangEntity> list = listRukuZixiangByTiaomaBianma(tiaomaBianma);
+        if (list == null || list.isEmpty()) {
+            throw new DataException("鏈壘鍒板搴旂殑鑰楁潗鍏ュ簱瀛愰」锛�" + tiaomaBianma);
+        }
+
+        LimsHaocaiRukuZixiangEntity zixiang = list.get(0);
+        String bizStatus = trimToEmpty(zixiang.getBizStatus());
+        if (BIZ_STATUS_YICHUKU.equalsIgnoreCase(bizStatus)) {
+            throw new DataException("璇ヨ�楁潗宸插嚭搴擄紝涓嶈兘閲嶅鍊熷嚭锛�" + tiaomaBianma);
+        }
+        if (!isBorrowableStatus(bizStatus)) {
+            throw new DataException("璇ヨ�楁潗鐘舵�佷笉鍏佽鍊熷嚭锛�" + bizStatus);
+        }
+
+        BigDecimal jiechuShuliang = parseRequiredDecimal(jiechu.getJiechuShuliang(),
+                "鍊熷嚭鏁伴噺涓嶈兘涓虹┖鎴栨牸寮忔棤鏁�");
+        BigDecimal shengyuliang = parseRequiredDecimal(zixiang.getShengyuliang(),
+                "鑰楁潗鍓╀綑閲忔棤鏁堬細" + tiaomaBianma);
+        if (jiechuShuliang.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new DataException("鍊熷嚭鏁伴噺蹇呴』澶т簬0");
+        }
+        if (jiechuShuliang.compareTo(shengyuliang) != 0) {
+            throw new DataException("鑰楁潗鐘舵�佸凡鍙樺寲锛岃鍒锋柊鍚庨噸鏂版彁浜�");
+        }
+
+        LimsHaocaiRukuZixiangEntity update = new LimsHaocaiRukuZixiangEntity();
+        update.setId(zixiang.getId());
+        update.setBizStatus(BIZ_STATUS_YICHUKU);
+        update.setShengyuliang("0");
+        LambdaQueryWrapper<LimsHaocaiRukuZixiangEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiRukuZixiangEntity::getId, zixiang.getId());
+        wrapper.in(LimsHaocaiRukuZixiangEntity::getBizStatus, BIZ_STATUS_WEICHUKU, BIZ_STATUS_YIKAIPING);
+        wrapper.eq(LimsHaocaiRukuZixiangEntity::getShengyuliang, zixiang.getShengyuliang());
+        int updated = zixiangMapper.update(update, wrapper);
+        if (updated <= 0) {
+            throw new DataException("鑰楁潗鐘舵�佸凡鍙樺寲锛岃鍒锋柊鍚庨噸鏂版彁浜�");
+        }
+
+        updateRukuJieyuShuliang(zixiang, false, BIZ_STATUS_YICHUKU);
+        zixiang.setBizStatus(BIZ_STATUS_YICHUKU);
+        zixiang.setShengyuliang("0");
+        haocaiLogMapper.insert(buildJiechuLog(jiechu, zixiang));
+        log.info("鑰楁潗鍊熷嚭鎻愪氦瀹屾垚锛歵iaomaBianma={}, zixiangId={}", tiaomaBianma, zixiang.getId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "GUIHUAN_SUBMIT", label = "鑰楁潗褰掕繕鎻愪氦", bizType = "HAOCAI",
+            bizCodeExpr = "", bizCodeRequired = false, targetTable = "lims_haocai_jiechu",
+            targetIdExpr = "#guihuan.id", diff = true, entityClass = LimsHaocaiJiechuEntity.class)
+    public void submitGuihuan(LimsHaocaiJiechuEntity guihuan) throws DataException {
+        String tiaomaBianma = trimToEmpty(guihuan == null ? null : guihuan.getTiaomaBianma());
+        if (tiaomaBianma.isEmpty()) {
+            throw new DataException("鏉$爜缂栫爜涓嶈兘涓虹┖");
+        }
+
+        LimsHaocaiJiechuEntity lentRecord = getLentJiechuByTiaomaBianma(tiaomaBianma);
+        if (lentRecord == null) {
+            throw new DataException("鏈壘鍒板凡鍊熷嚭鐨勮�楁潗鍊熷嚭璁板綍锛�" + tiaomaBianma);
+        }
+
+        LimsHaocaiJiechuEntity jiechuUpdate = new LimsHaocaiJiechuEntity();
+        jiechuUpdate.setId(lentRecord.getId());
+        jiechuUpdate.setBizStatus(JIECHU_STATUS_RETURNED);
+        jiechuUpdate.setGuihuanRen(guihuan.getGuihuanRen());
+        jiechuUpdate.setGuihuanRiqi(guihuan.getGuihuanRiqi());
+        jiechuUpdate.setGuihuanRenSign(guihuan.getGuihuanRenSign());
+        LambdaQueryWrapper<LimsHaocaiJiechuEntity> jiechuWrapper = new LambdaQueryWrapper<>();
+        jiechuWrapper.eq(LimsHaocaiJiechuEntity::getId, lentRecord.getId());
+        jiechuWrapper.eq(LimsHaocaiJiechuEntity::getBizStatus, JIECHU_STATUS_LENT);
+        int jiechuUpdated = jiechuMapper.update(jiechuUpdate, jiechuWrapper);
+        if (jiechuUpdated <= 0) {
+            throw new DataException("璇ヨ�楁潗鍊熷嚭璁板綍鐘舵�佸凡鍙樺寲锛屼笉鑳介噸澶嶅綊杩橈細" + tiaomaBianma);
+        }
+
+        List<LimsHaocaiRukuZixiangEntity> list = listRukuZixiangByTiaomaBianma(tiaomaBianma);
+        if (list == null || list.isEmpty()) {
+            throw new DataException("鏈壘鍒板搴旂殑鑰楁潗鍏ュ簱瀛愰」锛�" + tiaomaBianma);
+        }
+
+        LimsHaocaiRukuZixiangEntity zixiang = list.get(0);
+        String restoreShuliang = trimToEmpty(lentRecord.getJiechuShuliang());
+        BigDecimal guihuanShuliang = parseRequiredDecimal(restoreShuliang,
+                "鍊熷嚭璁板綍鍊熷嚭鏁伴噺鏃犳晥锛�" + tiaomaBianma);
+        if (guihuanShuliang.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new DataException("鍊熷嚭璁板綍鍊熷嚭鏁伴噺蹇呴』澶т簬0锛�" + tiaomaBianma);
+        }
+        BigDecimal zongliang = parseRequiredDecimal(zixiang.getZongliang(),
+                "鑰楁潗鎬婚噺鏃犳晥锛�" + tiaomaBianma);
+        if (guihuanShuliang.compareTo(zongliang) > 0) {
+            throw new DataException("褰掕繕鏁伴噺涓嶈兘澶т簬鑰楁潗鎬婚噺锛�" + tiaomaBianma);
+        }
+        LimsHaocaiRukuZixiangEntity zixiangUpdate = new LimsHaocaiRukuZixiangEntity();
+        zixiangUpdate.setId(zixiang.getId());
+        String newBizStatus = resolveAvailableBizStatus(guihuanShuliang, zixiang);
+        zixiangUpdate.setBizStatus(newBizStatus);
+        zixiangUpdate.setShengyuliang(toPlainString(guihuanShuliang));
+        LambdaQueryWrapper<LimsHaocaiRukuZixiangEntity> zixiangWrapper = new LambdaQueryWrapper<>();
+        zixiangWrapper.eq(LimsHaocaiRukuZixiangEntity::getId, zixiang.getId());
+        zixiangWrapper.eq(LimsHaocaiRukuZixiangEntity::getBizStatus, BIZ_STATUS_YICHUKU);
+        int zixiangUpdated = zixiangMapper.update(zixiangUpdate, zixiangWrapper);
+        if (zixiangUpdated <= 0) {
+            throw new DataException("璇ヨ�楁潗鎵规鐘舵�佷笉鍏佽褰掕繕锛�" + zixiang.getBizStatus());
+        }
+
+        zixiang.setShengyuliang(toPlainString(guihuanShuliang));
+        updateRukuJieyuShuliang(zixiang, guihuanShuliang, true, BIZ_STATUS_YICHUKU);
+        zixiang.setBizStatus(newBizStatus);
+        haocaiLogMapper.insert(buildGuihuanLog(guihuan, lentRecord, zixiang));
+        log.info("鑰楁潗褰掕繕鎻愪氦瀹屾垚锛歵iaomaBianma={}, jiechuId={}, zixiangId={}",
+                tiaomaBianma, lentRecord.getId(), zixiang.getId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "CHUKU_SUBMIT", label = "鑰楁潗鍑哄簱鎻愪氦", bizType = "HAOCAI",
+            bizCodeExpr = "", bizCodeRequired = false, targetTable = "lims_haocai_chuku",
+            targetIdExpr = "#chuku.id", diff = true, entityClass = LimsHaocaiChukuEntity.class)
+    public void submitChuku(LimsHaocaiChukuEntity chuku) throws DataException {
+        if (chuku == null) {
+            throw new DataException("鍑哄簱琛ㄥ崟鏁版嵁涓嶈兘涓虹┖");
+        }
+        String chukuId = trimToEmpty(chuku.getId());
+        if (chukuId.isEmpty()) {
+            throw new DataException("鍑哄簱鍗曚富閿�(f_id)涓嶈兘涓虹┖");
+        }
+        String shenlingDanhao = trimToEmpty(chuku.getShenlingDanhao());
+        if (shenlingDanhao.isEmpty()) {
+            throw new DataException("鐢抽鍗曞彿涓嶈兘涓虹┖");
+        }
+        List<LimsHaocaiChukuZixiangEntity> chukuZixiangList = listChukuZixiang(chukuId);
+        if (chukuZixiangList == null || chukuZixiangList.isEmpty()) {
+            throw new DataException("鍑哄簱瀛愰」涓嶈兘涓虹┖锛�" + chukuId);
+        }
+
+        LimsHaocaiChukuShenqingEntity shenqing = getApprovedChukuShenqingByDanhao(shenlingDanhao);
+        if (shenqing == null) {
+            throw new DataException("鐢抽鍗曚笉瀛樺湪鎴栨湭閫氳繃锛�" + shenlingDanhao);
+        }
+
+        int count = 0;
+        for (LimsHaocaiChukuZixiangEntity chukuZixiang : chukuZixiangList) {
+            submitChukuZixiang(chuku, shenqing, chukuZixiang);
+            count++;
+        }
+
+        log.info("鑰楁潗鍑哄簱鎻愪氦瀹屾垚锛歴henlingDanhao={}, count={}", shenlingDanhao, count);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "XIAOHUI_SUBMIT", label = "鑰楁潗閿�姣佹彁浜�", bizType = "HAOCAI",
+            bizCodeExpr = "#xiaohuiDanhao", bizCodeRequired = false,
+            targetTable = "lims_haocai_ruku_zixiang",
+            targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).firstHaocaiDestroyItem(#xiaohuiDanhao)",
+            diff = true, entityClass = LimsHaocaiRukuZixiangEntity.class)
+    public void submitXiaohui(String xiaohuiDanhao) throws DataException {
+        String danhao = trimToEmpty(xiaohuiDanhao);
+        if (danhao.isEmpty()) {
+            throw new DataException("閿�姣佸崟鍙蜂笉鑳戒负绌�");
+        }
+
+        LimsHaocaiXiaohuiShenqingEntity shenqing = getXiaohuiShenqingByDanhao(danhao);
+        if (shenqing == null) {
+            throw new DataException("閿�姣佺敵璇蜂笉瀛樺湪锛�" + danhao);
+        }
+
+        List<LimsHaocaiXiaohuiShenqingZixiangEntity> zixiangList = listXiaohuiZixiang(shenqing.getId());
+        if (zixiangList.isEmpty()) {
+            throw new DataException("閿�姣佺敵璇峰瓙椤逛笉鑳戒负绌猴細" + danhao);
+        }
+
+        int count = 0;
+        for (LimsHaocaiXiaohuiShenqingZixiangEntity xiaohuiZixiang : zixiangList) {
+            String tiaomaBianma = trimToEmpty(xiaohuiZixiang.getTiaomaBianma());
+            if (tiaomaBianma.isEmpty()) {
+                throw new DataException("閿�姣佺敵璇峰瓙椤规潯鐮佺紪鐮佷笉鑳戒负绌�");
+            }
+
+            LimsHaocaiRukuZixiangEntity rukuZixiang = getUniqueRukuZixiangByTiaomaBianma(tiaomaBianma);
+            if (rukuZixiang == null) {
+                throw new DataException("鏈壘鍒板搴旂殑鑰楁潗鍏ュ簱瀛愰」锛�" + tiaomaBianma);
+            }
+            if (BIZ_STATUS_YIXIAOHUI.equalsIgnoreCase(trimToEmpty(rukuZixiang.getBizStatus()))) {
+                throw new DataException("璇ヨ�楁潗宸查攢姣侊紝涓嶈兘閲嶅閿�姣侊細" + tiaomaBianma);
+            }
+
+            BigDecimal xiaohuiShuliang = parseRequiredDecimal(rukuZixiang.getShengyuliang(),
+                    "鑰楁潗鍓╀綑閲忔棤鏁堬細" + tiaomaBianma);
+            if (xiaohuiShuliang.compareTo(BigDecimal.ZERO) < 0) {
+                throw new DataException("鑰楁潗鍓╀綑閲忎笉鑳藉皬浜�0锛�" + tiaomaBianma);
+            }
+
+            LimsHaocaiRukuZixiangEntity rukuZixiangUpdate = new LimsHaocaiRukuZixiangEntity();
+            rukuZixiangUpdate.setId(rukuZixiang.getId());
+            rukuZixiangUpdate.setBizStatus(BIZ_STATUS_YIXIAOHUI);
+            rukuZixiangUpdate.setShengyuliang("0");
+            int updated = zixiangMapper.updateById(rukuZixiangUpdate);
+            if (updated <= 0) {
+                throw new DataException("鑰楁潗閿�姣佸け璐ワ細" + tiaomaBianma);
+            }
+
+            updateRukuJieyuShuliang(rukuZixiang, BIZ_STATUS_YIXIAOHUI);
+            rukuZixiang.setBizStatus(BIZ_STATUS_YIXIAOHUI);
+            rukuZixiang.setShengyuliang("0");
+            haocaiLogMapper.insert(buildXiaohuiLog(shenqing, xiaohuiZixiang, rukuZixiang, xiaohuiShuliang));
+            markXiaohuiZixiangDestroyed(xiaohuiZixiang);
+            count++;
+        }
+
+        log.info("鑰楁潗閿�姣佹彁浜ゅ畬鎴愶細xiaohuiDanhao={}, xiaohuiShenqingId={}, count={}",
+                danhao, shenqing.getId(), count);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "UPDATE", label = "鑰楁潗鎵归噺杞緟閿�姣�", bizType = "HAOCAI",
+            bizCodeExpr = "#result > 0 ? 'HAOCAI_RUKU_BATCH' : ''", targetTable = "lims_haocai_ruku_zixiang",
+            targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).firstHaocaiRukuItem(#rukuIds)",
+            diff = true, entityClass = LimsHaocaiRukuZixiangEntity.class)
+    public int batchMarkRukuZixiangDaixiaohui(List<String> rukuIds) throws DataException {
+        List<String> ids = normalizeIds(rukuIds);
+        if (ids.isEmpty()) {
+            throw new DataException("鍏ュ簱鍗曚富閿暟缁勪笉鑳戒负绌�");
+        }
+        UserInfo userInfo = UserProvider.getUser();
+        String currentUserId = userInfo == null ? "" : trimToEmpty(userInfo.getUserId());
+        if (currentUserId.isEmpty()) {
+            throw new DataException("鏈幏鍙栧埌褰撳墠鐧诲綍鐢ㄦ埛");
+        }
+
+        LambdaQueryWrapper<LimsHaocaiRukuZixiangEntity> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(LimsHaocaiRukuZixiangEntity::getForeignId, ids);
+        queryWrapper.and(w -> w.isNull(LimsHaocaiRukuZixiangEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiRukuZixiangEntity::getDeleteMark, 1));
+        List<LimsHaocaiRukuZixiangEntity> zixiangList = zixiangMapper.selectList(queryWrapper);
+
+        List<String> zixiangIds = new ArrayList<>();
+        for (LimsHaocaiRukuZixiangEntity zixiang : zixiangList) {
+            BigDecimal shengyuliang = parseOptionalDecimal(zixiang.getShengyuliang());
+            if (shengyuliang != null && shengyuliang.compareTo(BigDecimal.ZERO) > 0) {
+                zixiangIds.add(zixiang.getId());
+            }
+        }
+        if (zixiangIds.isEmpty()) {
+            log.info("鑰楁潗鍏ュ簱鎵归噺杞緟閿�姣佹棤鍙洿鏂板瓙椤癸細rukuIds={}", ids);
+            return 0;
+        }
+
+        LimsHaocaiRukuZixiangEntity update = new LimsHaocaiRukuZixiangEntity();
+        update.setBizStatus(BIZ_STATUS_DAIXIAOHUI);
+        update.setZhuanxiaohuiRen(currentUserId);
+        LambdaQueryWrapper<LimsHaocaiRukuZixiangEntity> updateWrapper = new LambdaQueryWrapper<>();
+        updateWrapper.in(LimsHaocaiRukuZixiangEntity::getId, zixiangIds);
+        int updated = zixiangMapper.update(update, updateWrapper);
+        updateRukuBizStatus(ids, BIZ_STATUS_DAIXIAOHUI);
+        log.info("鑰楁潗鍏ュ簱鎵归噺杞緟閿�姣佸畬鎴愶細rukuIds={}, zixiangCount={}", ids, updated);
+        return updated;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "XIAOHUI_START", label = "鑰楁潗閿�姣佺敵璇峰彂璧�", bizType = "HAOCAI",
+            bizCodeExpr = "#result > 0 ? #flowId : ''", targetTable = "lims_haocai_ruku_zixiang",
+            targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).firstHaocaiDestroyItemByFlow(#flowId, #taskId)",
+            diff = true, entityClass = LimsHaocaiRukuZixiangEntity.class)
+    public int startXiaohuiShenqing(String flowId, String taskId) throws DataException {
+        String flow = trimToEmpty(flowId);
+        String task = trimToEmpty(taskId);
+        if (flow.isEmpty()) {
+            throw new DataException("娴佺▼id(flow_id)涓嶈兘涓虹┖");
+        }
+        if (task.isEmpty()) {
+            throw new DataException("娴佺▼浠诲姟id(task_id)涓嶈兘涓虹┖");
+        }
+        LimsHaocaiXiaohuiShenqingEntity shenqing = getXiaohuiShenqingByFlow(flow, task);
+        if (shenqing == null) {
+            throw new DataException("閿�姣佺敵璇蜂笉瀛樺湪锛歠low_id=" + flow + ", task_id=" + task);
+        }
+        String id = shenqing.getId();
+        String xiaohuiDanhao = trimToEmpty(shenqing.getXiaohuiDanhao());
+        if (xiaohuiDanhao.isEmpty()) {
+            throw new DataException("閿�姣佸崟鍙蜂笉鑳戒负绌猴細" + id);
+        }
+
+        List<LimsHaocaiXiaohuiShenqingZixiangEntity> xiaohuiZixiangList = listXiaohuiZixiang(id);
+        if (xiaohuiZixiangList == null || xiaohuiZixiangList.isEmpty()) {
+            throw new DataException("閿�姣佺敵璇峰瓙椤逛笉鑳戒负绌猴細" + xiaohuiDanhao);
+        }
+
+        int updated = 0;
+        for (LimsHaocaiXiaohuiShenqingZixiangEntity xiaohuiZixiang : xiaohuiZixiangList) {
+            String tiaomaBianma = trimToEmpty(xiaohuiZixiang.getTiaomaBianma());
+            if (tiaomaBianma.isEmpty()) {
+                throw new DataException("閿�姣佺敵璇峰瓙椤规潯鐮佷笉鑳戒负绌猴細" + xiaohuiDanhao);
+            }
+            LimsHaocaiRukuZixiangEntity update = new LimsHaocaiRukuZixiangEntity();
+            update.setBizStatus(BIZ_STATUS_DAIXIAOHUI);
+            update.setXiaohuiDanhao(xiaohuiDanhao);
+
+            LambdaQueryWrapper<LimsHaocaiRukuZixiangEntity> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LimsHaocaiRukuZixiangEntity::getTiaomaBianma, tiaomaBianma);
+            wrapper.and(w -> w.isNull(LimsHaocaiRukuZixiangEntity::getDeleteMark)
+                    .or()
+                    .ne(LimsHaocaiRukuZixiangEntity::getDeleteMark, 1));
+            int rowUpdated = zixiangMapper.update(update, wrapper);
+            if (rowUpdated <= 0) {
+                throw new DataException("鏈壘鍒板搴旂殑鑰楁潗鍏ュ簱瀛愰」锛�" + tiaomaBianma);
+            }
+            updated += rowUpdated;
+        }
+        log.info("鑰楁潗閿�姣佺敵璇峰彂璧峰畬鎴愶細xiaohuiShenqingId={}, xiaohuiDanhao={}, zixiangCount={}",
+                id, xiaohuiDanhao, updated);
+        return updated;
+    }
+
+    @Override
+    public List<LimsHaocaiRukuZixiangEntity> listRukuZixiangByTiaomaBianma(String tiaomaBianma) {
+        LambdaQueryWrapper<LimsHaocaiRukuZixiangEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiRukuZixiangEntity::getTiaomaBianma, trimToEmpty(tiaomaBianma));
+        wrapper.and(w -> w.isNull(LimsHaocaiRukuZixiangEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiRukuZixiangEntity::getDeleteMark, 1));
+        wrapper.orderByDesc(LimsHaocaiRukuZixiangEntity::getRukuShijian);
+        wrapper.orderByAsc(LimsHaocaiRukuZixiangEntity::getBianhao);
+        return zixiangMapper.selectList(wrapper);
+    }
+
+    @Override
+    public LimsHaocaiChukuShenqingZixiangEntity getChukuShenqingZixiangForChuku(String shenlingDanhao,
+                                                                                String tiaomaBianma) throws DataException {
+        String danhao = trimToEmpty(shenlingDanhao);
+        String tiaoma = trimToEmpty(tiaomaBianma);
+        if (danhao.isEmpty()) {
+            throw new DataException("鐢抽鍗曞彿涓嶈兘涓虹┖");
+        }
+        if (tiaoma.isEmpty()) {
+            throw new DataException("鏉$爜缂栫爜涓嶈兘涓虹┖");
+        }
+
+        LimsHaocaiRukuZixiangEntity rukuZixiang = getUniqueRukuZixiangByTiaomaBianma(tiaoma);
+        if (rukuZixiang == null) {
+            throw new DataException("鏈壘鍒板搴旂殑鑰楁潗鍏ュ簱瀛愰」锛�" + tiaoma);
+        }
+        if (!isChukuAvailableStatus(rukuZixiang.getBizStatus())) {
+            throw new DataException("璇ヨ�楁潗鐘舵�佷笉鍏佽鍑哄簱锛�" + trimToEmpty(rukuZixiang.getBizStatus()));
+        }
+        BigDecimal shengyuliang = parseRequiredDecimal(rukuZixiang.getShengyuliang(),
+                "鑰楁潗鍓╀綑閲忔棤鏁堬細" + tiaoma);
+        if (shengyuliang.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new DataException("璇ヨ�楁潗鍓╀綑閲忎笉瓒筹紝鏃犳硶鍑哄簱锛�" + tiaoma);
+        }
+
+        LimsHaocaiChukuShenqingEntity shenqing = getApprovedChukuShenqingByDanhao(danhao);
+        if (shenqing == null) {
+            throw new DataException("鐢抽鍗曚笉瀛樺湪鎴栨湭閫氳繃锛�" + danhao);
+        }
+        LimsHaocaiChukuShenqingZixiangEntity chukuZixiang = getChukuShenqingZixiang(shenqing.getId(), tiaoma);
+        if (chukuZixiang == null) {
+            throw new DataException("鏉$爜缂栫爜涓嶅湪璇ョ敵棰嗗崟瀛愯〃涓細" + tiaoma);
+        }
+        if ("yes".equalsIgnoreCase(trimToEmpty(chukuZixiang.getShifouYichuku()))) {
+            throw new DataException("璇ョ敵棰嗗瓙椤瑰凡鍑哄簱锛屼笉鑳介噸澶嶅嚭搴擄細" + tiaoma);
+        }
+        chukuZixiang.setYongtu(shenqing.getYongtu());
+        chukuZixiang.setShifouShuangrenChuku(rukuZixiang.getShifouShuangrenChuku());
+        return chukuZixiang;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "YANSHOU_SUBMIT", label = "鑰楁潗鍏ュ簱楠屾敹鎻愪氦", bizType = "HAOCAI",
+            bizCodeExpr = "#flowId", bizCodeRequired = false, targetTable = "lims_haocai_yanshou_shenqing",
+            targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).haocaiAcceptanceRequestByFlow(#flowId, #taskId)",
+            diff = true, entityClass = LimsHaocaiYanshouShenqingEntity.class)
+    public void submitRukuYanshou(String flowId, String taskId) throws DataException {
+        String flow = trimToEmpty(flowId);
+        String task = trimToEmpty(taskId);
+        if (flow.isEmpty()) {
+            throw new DataException("娴佺▼id(flow_id)涓嶈兘涓虹┖");
+        }
+        if (task.isEmpty()) {
+            throw new DataException("娴佺▼浠诲姟id(task_id)涓嶈兘涓虹┖");
+        }
+
+        LimsHaocaiRukuYanshouEntity yanshou = getUniqueRukuYanshouByFlow(flow, task);
+        String shenqingId = trimToEmpty(yanshou.getShenqingId());
+        if (shenqingId.isEmpty()) {
+            throw new DataException("鑰楁潗鍏ュ簱楠屾敹璁板綍鏈叧鑱旈獙鏀剁敵璇凤細" + yanshou.getId());
+        }
+
+        LambdaUpdateWrapper<LimsHaocaiYanshouShenqingEntity> update = new LambdaUpdateWrapper<>();
+        update.eq(LimsHaocaiYanshouShenqingEntity::getId, shenqingId)
+                .and(w -> w.isNull(LimsHaocaiYanshouShenqingEntity::getDeleteMark)
+                        .or().ne(LimsHaocaiYanshouShenqingEntity::getDeleteMark, 1))
+                .set(LimsHaocaiYanshouShenqingEntity::getYanshouId, yanshou.getId());
+        if (yanshouShenqingMapper.update(null, update) != 1) {
+            throw new DataException("鑰楁潗楠屾敹鐢宠涓嶅瓨鍦ㄦ垨宸插垹闄わ細" + shenqingId);
+        }
+        log.info("鑰楁潗鍏ュ簱楠屾敹鎻愪氦瀹屾垚锛歠lowId={}, taskId={}, yanshouId={}, shenqingId={}",
+                flow, task, yanshou.getId(), shenqingId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "YANSHOU_FINISH", label = "鑰楁潗鍏ュ簱楠屾敹瀹屾垚", bizType = "HAOCAI",
+            bizCodeExpr = "#flowId", bizCodeRequired = false, targetTable = "lims_haocai_yanshou_shenqing",
+            targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).haocaiAcceptanceRequestByFlow(#flowId, #taskId)",
+            diff = true, entityClass = LimsHaocaiYanshouShenqingEntity.class)
+    public void finishRukuYanshou(String flowId, String taskId) throws DataException {
+        String flow = trimToEmpty(flowId);
+        String task = trimToEmpty(taskId);
+        if (flow.isEmpty()) {
+            throw new DataException("娴佺▼id(flow_id)涓嶈兘涓虹┖");
+        }
+        if (task.isEmpty()) {
+            throw new DataException("娴佺▼浠诲姟id(task_id)涓嶈兘涓虹┖");
+        }
+
+        LimsHaocaiRukuYanshouEntity yanshou = getUniqueRukuYanshouByFlow(flow, task);
+        String shenqingId = trimToEmpty(yanshou.getShenqingId());
+        if (shenqingId.isEmpty()) {
+            throw new DataException("鑰楁潗鍏ュ簱楠屾敹璁板綍鏈叧鑱旈獙鏀剁敵璇凤細" + yanshou.getId());
+        }
+
+        boolean approved = Integer.valueOf(FLOW_STATE_APPROVED).equals(yanshou.getFlowState());
+        LambdaUpdateWrapper<LimsHaocaiYanshouShenqingEntity> update = new LambdaUpdateWrapper<>();
+        update.eq(LimsHaocaiYanshouShenqingEntity::getId, shenqingId)
+                .and(w -> w.isNull(LimsHaocaiYanshouShenqingEntity::getDeleteMark)
+                        .or().ne(LimsHaocaiYanshouShenqingEntity::getDeleteMark, 1));
+        if (approved) {
+            update.set(LimsHaocaiYanshouShenqingEntity::getBizStatus,
+                    YANSHOU_SHENQING_STATUS_YIYANSHOU);
+        } else {
+            update.set(LimsHaocaiYanshouShenqingEntity::getBizStatus,
+                            YANSHOU_SHENQING_STATUS_DAIYANSHOU)
+                    .set(LimsHaocaiYanshouShenqingEntity::getShiyanYuan, null)
+                    .set(LimsHaocaiYanshouShenqingEntity::getRenwuLingquShijian, null)
+                    .set(LimsHaocaiYanshouShenqingEntity::getYanshouId, null);
+        }
+        if (yanshouShenqingMapper.update(null, update) != 1) {
+            throw new DataException("鑰楁潗楠屾敹鐢宠涓嶅瓨鍦ㄦ垨宸插垹闄わ細" + shenqingId);
+        }
+        log.info("鑰楁潗鍏ュ簱楠屾敹瀹屾垚澶勭悊鎴愬姛锛歠lowId={}, taskId={}, yanshouId={}, flowState={}, approved={}",
+                flow, task, yanshou.getId(), yanshou.getFlowState(), approved);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(action = "YANSHOU_CLAIM", label = "棰嗗彇鑰楁潗楠屾敹鐢宠", bizType = "HAOCAI",
+            bizCodeExpr = "#shenqingId", bizCodeRequired = false, targetTable = "lims_haocai_yanshou_shenqing",
+            targetIdExpr = "#shenqingId", diff = true,
+            entityClass = LimsHaocaiYanshouShenqingEntity.class)
+    public void lingquYanshouShenqing(String shenqingId) throws DataException {
+        String id = trimToEmpty(shenqingId);
+        if (id.isEmpty()) {
+            throw new DataException("鑰楁潗楠屾敹鐢宠涓婚敭(shenqing_id)涓嶈兘涓虹┖");
+        }
+
+        LimsHaocaiYanshouShenqingEntity shenqing = yanshouShenqingMapper.selectById(id);
+        if (shenqing == null || Integer.valueOf(1).equals(shenqing.getDeleteMark())) {
+            throw new DataException("鑰楁潗楠屾敹鐢宠涓嶅瓨鍦細" + id);
+        }
+        if (!YANSHOU_SHENQING_STATUS_DAIYANSHOU.equals(shenqing.getBizStatus())) {
+            throw new DataException("鑰楁潗楠屾敹鐢宠褰撳墠鐘舵�佷负銆�"
+                    + yanshouShenqingStatusLabel(shenqing.getBizStatus()) + "銆戯紝浠呫�愬緟楠屾敹銆戠姸鎬佸彲棰嗗彇");
+        }
+
+        UserInfo userInfo = UserProvider.getUser();
+        if (userInfo == null || trimToEmpty(userInfo.getUserId()).isEmpty()) {
+            throw new DataException("褰撳墠鐧诲綍鐢ㄦ埛涓嶅瓨鍦�");
+        }
+        LambdaUpdateWrapper<LimsHaocaiYanshouShenqingEntity> update = new LambdaUpdateWrapper<>();
+        update.eq(LimsHaocaiYanshouShenqingEntity::getId, id)
+                .eq(LimsHaocaiYanshouShenqingEntity::getBizStatus, YANSHOU_SHENQING_STATUS_DAIYANSHOU)
+                .and(w -> w.isNull(LimsHaocaiYanshouShenqingEntity::getDeleteMark)
+                        .or().ne(LimsHaocaiYanshouShenqingEntity::getDeleteMark, 1))
+                .set(LimsHaocaiYanshouShenqingEntity::getBizStatus, YANSHOU_SHENQING_STATUS_YANSHOUZHONG)
+                .set(LimsHaocaiYanshouShenqingEntity::getShiyanYuan, userInfo.getUserId())
+                .set(LimsHaocaiYanshouShenqingEntity::getRenwuLingquShijian, new Date());
+        if (yanshouShenqingMapper.update(null, update) != 1) {
+            throw new DataException("鑰楁潗楠屾敹鐢宠鐘舵�佸凡鍙樺寲锛岃鍒锋柊鍚庨噸鏂伴鍙�");
+        }
+    }
+
+    private LimsHaocaiRukuYanshouEntity getUniqueRukuYanshouByFlow(String flowId, String taskId)
+            throws DataException {
+        LambdaQueryWrapper<LimsHaocaiRukuYanshouEntity> query = new LambdaQueryWrapper<>();
+        query.eq(LimsHaocaiRukuYanshouEntity::getFlowId, flowId)
+                .eq(LimsHaocaiRukuYanshouEntity::getFlowTaskId, taskId)
+                .and(w -> w.isNull(LimsHaocaiRukuYanshouEntity::getDeleteMark)
+                        .or().ne(LimsHaocaiRukuYanshouEntity::getDeleteMark, 1));
+        List<LimsHaocaiRukuYanshouEntity> list = rukuYanshouMapper.selectList(query);
+        if (list == null || list.isEmpty()) {
+            throw new DataException("鑰楁潗鍏ュ簱楠屾敹璁板綍涓嶅瓨鍦細flow_id=" + flowId + ", task_id=" + taskId);
+        }
+        if (list.size() > 1) {
+            throw new DataException("澶氫釜鑰楁潗鍏ュ簱楠屾敹璁板綍鍏宠仈鍚屼竴娴佺▼浠诲姟锛歵ask_id=" + taskId);
+        }
+        return list.get(0);
+    }
+
+    private LimsHaocaiRukuZixiangEntity buildZixiang(LimsHaocaiRukuEntity ruku,
+                                                     String tiaomaBianma, String bianhao,
+                                                     String zongliang, String yanshouZhuangtai) {
+        LimsHaocaiRukuZixiangEntity record = new LimsHaocaiRukuZixiangEntity();
+        record.setId(RandomUtil.uuId());
+        record.setForeignId(ruku.getId());
+        record.setRukuId(ruku.getId());
+        record.setTiaomaBianma(tiaomaBianma);
+        record.setBianhao(bianhao);
+        record.setZongliang(zongliang);
+        record.setShengyuliang(zongliang);
+        record.setBizStatus(BIZ_STATUS_WEICHUKU);
+        record.setYanshouZhuangtai(yanshouZhuangtai);
+        record.setMingcheng(ruku.getHaocaiMingcheng());
+        record.setFenlei(ruku.getHaocaiFenlei());
+        record.setGuige(ruku.getGuige());
+        record.setPihao(ruku.getPihao());
+        record.setDanwei(ruku.getRongliangDanwei());
+        record.setCunfangWeizhi(ruku.getCunfangWeizhi());
+        record.setYouxiaoqi(ruku.getYouxiaoqi());
+        record.setKaipingYouxiaoqi(ruku.getKaipingYouxiaoqi());
+        record.setKaipingYouxiaoqiDanwei(ruku.getKaipingYouxiaoqiDanwei());
+        record.setRukuShijian(ruku.getRukuShijian() != null ? ruku.getRukuShijian() : new Date());
+        record.setRukuRen(ruku.getRukuRen());
+        record.setShenheRen(ruku.getShenheRen());
+        record.setHaocaiId(ruku.getHaocaiId());
+        record.setShifouShuangrenChuku(ruku.getShifouShuangrenChuku());
+        return record;
+    }
+
+    private LimsHaocaiLogEntity buildRukuLog(LimsHaocaiRukuEntity ruku, LimsHaocaiRukuZixiangEntity zixiang) {
+        LimsHaocaiLogEntity record = new LimsHaocaiLogEntity();
+        record.setId(RandomUtil.uuId());
+        record.setFenlei(zixiang.getFenlei());
+        record.setTiaomaBianma(zixiang.getTiaomaBianma());
+        record.setBianhao(zixiang.getBianhao());
+        record.setMingcheng(zixiang.getMingcheng());
+        record.setGuige(zixiang.getGuige());
+        record.setPihao(zixiang.getPihao());
+        record.setChurukuShuliang(zixiang.getZongliang());
+        record.setShengyuShuliang(zixiang.getShengyuliang());
+        record.setDanwei(zixiang.getDanwei());
+        record.setCaozuoRen(zixiang.getRukuRen());
+        record.setFuheRen(zixiang.getShenheRen());
+        record.setCaozuoRiqi(zixiang.getRukuShijian());
+        record.setCaozuoLeixing(LOG_ACTION_RUKU);
+        record.setBeizhu(ruku.getBeizhu());
+        return record;
+    }
+
+    private LimsHaocaiLogEntity buildJiechuLog(LimsHaocaiJiechuEntity jiechu, LimsHaocaiRukuZixiangEntity zixiang) {
+        LimsHaocaiLogEntity record = new LimsHaocaiLogEntity();
+        record.setId(RandomUtil.uuId());
+        record.setFenlei(zixiang.getFenlei());
+        record.setTiaomaBianma(zixiang.getTiaomaBianma());
+        record.setBianhao(zixiang.getBianhao());
+        record.setMingcheng(zixiang.getMingcheng());
+        record.setGuige(zixiang.getGuige());
+        record.setPihao(zixiang.getPihao());
+        record.setChurukuShuliang(jiechu.getJiechuShuliang());
+        record.setShengyuShuliang(zixiang.getShengyuliang());
+        record.setDanwei(firstNotBlank(jiechu.getDanwei(), zixiang.getDanwei()));
+        record.setCaozuoRen(jiechu.getJiechuRen());
+        record.setCaozuoRiqi(jiechu.getJiechuRiqi() != null ? jiechu.getJiechuRiqi() : new Date());
+        record.setCaozuoLeixing(LOG_ACTION_JIECHU);
+        record.setBeizhu(jiechu.getYongtu());
+        return record;
+    }
+
+    private LimsHaocaiLogEntity buildGuihuanLog(LimsHaocaiJiechuEntity guihuan,
+                                                LimsHaocaiJiechuEntity lentRecord,
+                                                LimsHaocaiRukuZixiangEntity zixiang) {
+        LimsHaocaiLogEntity record = new LimsHaocaiLogEntity();
+        record.setId(RandomUtil.uuId());
+        record.setFenlei(zixiang.getFenlei());
+        record.setTiaomaBianma(zixiang.getTiaomaBianma());
+        record.setBianhao(zixiang.getBianhao());
+        record.setMingcheng(zixiang.getMingcheng());
+        record.setGuige(zixiang.getGuige());
+        record.setPihao(zixiang.getPihao());
+        record.setChurukuShuliang(lentRecord.getJiechuShuliang());
+        record.setShengyuShuliang(zixiang.getShengyuliang());
+        record.setDanwei(firstNotBlank(lentRecord.getDanwei(), zixiang.getDanwei()));
+        record.setCaozuoRen(firstNotBlank(guihuan.getGuihuanRen(), firstNotBlank(guihuan.getJiechuRen(), lentRecord.getJiechuRen())));
+        record.setCaozuoRiqi(guihuan.getGuihuanRiqi() != null ? guihuan.getGuihuanRiqi() : new Date());
+        record.setCaozuoLeixing(LOG_ACTION_GUIHUAN);
+        record.setBeizhu(firstNotBlank(guihuan.getYongtu(), lentRecord.getYongtu()));
+        return record;
+    }
+
+    private LimsHaocaiLogEntity buildChukuLog(LimsHaocaiChukuEntity chuku,
+                                              LimsHaocaiChukuZixiangEntity chukuZixiang,
+                                              LimsHaocaiRukuZixiangEntity rukuZixiang,
+                                              BigDecimal chukuliang) {
+        LimsHaocaiLogEntity record = new LimsHaocaiLogEntity();
+        record.setId(RandomUtil.uuId());
+        record.setFenlei(rukuZixiang.getFenlei());
+        record.setTiaomaBianma(rukuZixiang.getTiaomaBianma());
+        record.setBianhao(rukuZixiang.getBianhao());
+        record.setMingcheng(rukuZixiang.getMingcheng());
+        record.setGuige(rukuZixiang.getGuige());
+        record.setPihao(rukuZixiang.getPihao());
+        record.setChurukuShuliang(toPlainString(chukuliang));
+        record.setShengyuShuliang(rukuZixiang.getShengyuliang());
+        record.setDanwei(firstNotBlank(chukuZixiang.getDanwei(), rukuZixiang.getDanwei()));
+        record.setCaozuoRen(chuku.getChukuRen());
+        record.setFuheRen(chuku.getFuheRen());
+        record.setCaozuoRiqi(chuku.getChukuRiqi() != null ? chuku.getChukuRiqi() : new Date());
+        record.setCaozuoLeixing(LOG_ACTION_CHUKU);
+        record.setBeizhu(chukuZixiang.getYongtu());
+        return record;
+    }
+
+    private LimsHaocaiLogEntity buildXiaohuiLog(LimsHaocaiXiaohuiShenqingEntity shenqing,
+                                                LimsHaocaiXiaohuiShenqingZixiangEntity xiaohuiZixiang,
+                                                LimsHaocaiRukuZixiangEntity rukuZixiang,
+                                                BigDecimal xiaohuiShuliang) {
+        LimsHaocaiLogEntity record = new LimsHaocaiLogEntity();
+        record.setId(RandomUtil.uuId());
+        record.setFenlei(rukuZixiang.getFenlei());
+        record.setTiaomaBianma(rukuZixiang.getTiaomaBianma());
+        record.setBianhao(rukuZixiang.getBianhao());
+        record.setMingcheng(rukuZixiang.getMingcheng());
+        record.setGuige(rukuZixiang.getGuige());
+        record.setPihao(rukuZixiang.getPihao());
+        record.setChurukuShuliang(toPlainString(xiaohuiShuliang));
+        record.setShengyuShuliang(rukuZixiang.getShengyuliang());
+        record.setDanwei(rukuZixiang.getDanwei());
+        record.setCaozuoRen(shenqing.getShenqingRen());
+        record.setCaozuoRiqi(shenqing.getShenqingRiqi() != null ? shenqing.getShenqingRiqi() : new Date());
+        record.setCaozuoLeixing(LOG_ACTION_XIAOHUI);
+        record.setBeizhu(firstNotBlank(xiaohuiZixiang.getYuanyin(), firstNotBlank(shenqing.getYuanyin(), shenqing.getFangfa())));
+        return record;
+    }
+
+    private LimsHaocaiJiechuEntity getLentJiechuByTiaomaBianma(String tiaomaBianma) {
+        LambdaQueryWrapper<LimsHaocaiJiechuEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiJiechuEntity::getTiaomaBianma, trimToEmpty(tiaomaBianma));
+        wrapper.eq(LimsHaocaiJiechuEntity::getBizStatus, JIECHU_STATUS_LENT);
+        wrapper.and(w -> w.isNull(LimsHaocaiJiechuEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiJiechuEntity::getDeleteMark, 1));
+        wrapper.orderByDesc(LimsHaocaiJiechuEntity::getJiechuRiqi);
+        return jiechuMapper.selectList(wrapper).stream().findFirst().orElse(null);
+    }
+
+    private void submitChukuZixiang(LimsHaocaiChukuEntity chuku,
+                                    LimsHaocaiChukuShenqingEntity shenqing,
+                                    LimsHaocaiChukuZixiangEntity chukuZixiang) throws DataException {
+        String tiaomaBianma = trimToEmpty(chukuZixiang == null ? null : chukuZixiang.getTiaomaBianma());
+        if (tiaomaBianma.isEmpty()) {
+            throw new DataException("鍑哄簱瀛愰」鏉$爜缂栫爜涓嶈兘涓虹┖");
+        }
+
+        BigDecimal chukuliang = parseRequiredDecimal(chukuZixiang.getChukuliang(),
+                "鏈鍑哄簱閲忎笉鑳戒负绌烘垨鏍煎紡鏃犳晥锛�" + tiaomaBianma);
+        if (chukuliang.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new DataException("鏈鍑哄簱閲忓繀椤诲ぇ浜�0锛�" + tiaomaBianma);
+        }
+
+        LimsHaocaiRukuZixiangEntity rukuZixiang = getUniqueRukuZixiangByTiaomaBianma(tiaomaBianma);
+        if (rukuZixiang == null) {
+            throw new DataException("鏈壘鍒板搴旂殑鑰楁潗鍏ュ簱瀛愰」锛�" + tiaomaBianma);
+        }
+        String bizStatus = trimToEmpty(rukuZixiang.getBizStatus());
+        if (!isChukuAvailableStatus(bizStatus)) {
+            throw new DataException("璇ヨ�楁潗鐘舵�佷笉鍏佽鍑哄簱锛�" + bizStatus);
+        }
+
+        BigDecimal shengyuliang = parseRequiredDecimal(rukuZixiang.getShengyuliang(),
+                "鑰楁潗鍓╀綑閲忔棤鏁堬細" + tiaomaBianma);
+        if (chukuliang.compareTo(shengyuliang) > 0) {
+            throw new DataException("鏈鍑哄簱閲忎笉鑳藉ぇ浜庡墿浣欓噺锛�" + tiaomaBianma);
+        }
+
+        LimsHaocaiChukuShenqingZixiangEntity shenqingZixiang =
+                getChukuShenqingZixiang(shenqing.getId(), tiaomaBianma);
+        if (shenqingZixiang == null) {
+            throw new DataException("鏉$爜缂栫爜涓嶅湪璇ョ敵棰嗗崟瀛愯〃涓細" + tiaomaBianma);
+        }
+        if ("yes".equalsIgnoreCase(trimToEmpty(shenqingZixiang.getShifouYichuku()))) {
+            throw new DataException("璇ョ敵棰嗗瓙椤瑰凡鍑哄簱锛屼笉鑳介噸澶嶅嚭搴擄細" + tiaomaBianma);
+        }
+
+        BigDecimal newShengyuliang = shengyuliang.subtract(chukuliang);
+        String newBizStatus = newShengyuliang.compareTo(BigDecimal.ZERO) == 0
+                ? BIZ_STATUS_YICHUKU
+                : BIZ_STATUS_YIKAIPING;
+
+        LimsHaocaiRukuZixiangEntity rukuZixiangUpdate = new LimsHaocaiRukuZixiangEntity();
+        rukuZixiangUpdate.setId(rukuZixiang.getId());
+        rukuZixiangUpdate.setShengyuliang(toPlainString(newShengyuliang));
+        rukuZixiangUpdate.setBizStatus(newBizStatus);
+        LambdaQueryWrapper<LimsHaocaiRukuZixiangEntity> rukuZixiangWrapper = new LambdaQueryWrapper<>();
+        rukuZixiangWrapper.eq(LimsHaocaiRukuZixiangEntity::getId, rukuZixiang.getId());
+        rukuZixiangWrapper.in(LimsHaocaiRukuZixiangEntity::getBizStatus, BIZ_STATUS_WEICHUKU, BIZ_STATUS_YIKAIPING);
+        rukuZixiangWrapper.eq(LimsHaocaiRukuZixiangEntity::getShengyuliang, rukuZixiang.getShengyuliang());
+        int rukuZixiangUpdated = zixiangMapper.update(rukuZixiangUpdate, rukuZixiangWrapper);
+        if (rukuZixiangUpdated <= 0) {
+            throw new DataException("鑰楁潗鐘舵�佸凡鍙樺寲锛岃鍒锋柊鍚庨噸鏂版彁浜わ細" + tiaomaBianma);
+        }
+
+        markChukuShenqingZixiangOut(shenqingZixiang);
+        updateRukuJieyuShuliang(rukuZixiang, chukuliang, false, BIZ_STATUS_YICHUKU);
+        rukuZixiang.setShengyuliang(toPlainString(newShengyuliang));
+        rukuZixiang.setBizStatus(newBizStatus);
+        haocaiLogMapper.insert(buildChukuLog(chuku, chukuZixiang, rukuZixiang, chukuliang));
+    }
+
+    private List<LimsHaocaiXiaohuiShenqingZixiangEntity> listXiaohuiZixiang(String foreignId) {
+        LambdaQueryWrapper<LimsHaocaiXiaohuiShenqingZixiangEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiXiaohuiShenqingZixiangEntity::getForeignId, foreignId);
+        wrapper.and(w -> w.isNull(LimsHaocaiXiaohuiShenqingZixiangEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiXiaohuiShenqingZixiangEntity::getDeleteMark, 1));
+        return xiaohuiShenqingZixiangMapper.selectList(wrapper);
+    }
+
+    private List<LimsHaocaiChukuZixiangEntity> listChukuZixiang(String foreignId) {
+        LambdaQueryWrapper<LimsHaocaiChukuZixiangEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiChukuZixiangEntity::getForeignId, foreignId);
+        wrapper.and(w -> w.isNull(LimsHaocaiChukuZixiangEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiChukuZixiangEntity::getDeleteMark, 1));
+        return chukuZixiangMapper.selectList(wrapper);
+    }
+
+    private void markXiaohuiZixiangDestroyed(LimsHaocaiXiaohuiShenqingZixiangEntity xiaohuiZixiang) throws DataException {
+        LimsHaocaiXiaohuiShenqingZixiangEntity update = new LimsHaocaiXiaohuiShenqingZixiangEntity();
+        update.setId(xiaohuiZixiang.getId());
+        update.setShifouYixiaohui("yes");
+        int updated = xiaohuiShenqingZixiangMapper.updateById(update);
+        if (updated <= 0) {
+            throw new DataException("閿�姣佺敵璇峰瓙椤圭姸鎬佹洿鏂板け璐ワ細" + xiaohuiZixiang.getTiaomaBianma());
+        }
+    }
+
+    private void markChukuShenqingZixiangOut(LimsHaocaiChukuShenqingZixiangEntity shenqingZixiang) throws DataException {
+        LimsHaocaiChukuShenqingZixiangEntity update = new LimsHaocaiChukuShenqingZixiangEntity();
+        update.setId(shenqingZixiang.getId());
+        update.setShifouYichuku("yes");
+        LambdaQueryWrapper<LimsHaocaiChukuShenqingZixiangEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiChukuShenqingZixiangEntity::getId, shenqingZixiang.getId());
+        wrapper.and(w -> w.isNull(LimsHaocaiChukuShenqingZixiangEntity::getShifouYichuku)
+                .or()
+                .ne(LimsHaocaiChukuShenqingZixiangEntity::getShifouYichuku, "yes"));
+        int updated = chukuShenqingZixiangMapper.update(update, wrapper);
+        if (updated <= 0) {
+            throw new DataException("鐢抽瀛愰」宸插嚭搴擄紝涓嶈兘閲嶅鍑哄簱锛�" + shenqingZixiang.getTiaomaBianma());
+        }
+    }
+
+    private LimsHaocaiXiaohuiShenqingEntity getXiaohuiShenqingByDanhao(String xiaohuiDanhao) {
+        LambdaQueryWrapper<LimsHaocaiXiaohuiShenqingEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiXiaohuiShenqingEntity::getXiaohuiDanhao, trimToEmpty(xiaohuiDanhao));
+        wrapper.and(w -> w.isNull(LimsHaocaiXiaohuiShenqingEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiXiaohuiShenqingEntity::getDeleteMark, 1));
+        return xiaohuiShenqingMapper.selectList(wrapper).stream().findFirst().orElse(null);
+    }
+
+    private LimsHaocaiXiaohuiShenqingEntity getXiaohuiShenqingByFlow(String flowId, String taskId) {
+        LambdaQueryWrapper<LimsHaocaiXiaohuiShenqingEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiXiaohuiShenqingEntity::getFlowId, trimToEmpty(flowId));
+        wrapper.eq(LimsHaocaiXiaohuiShenqingEntity::getFlowTaskId, trimToEmpty(taskId));
+        wrapper.and(w -> w.isNull(LimsHaocaiXiaohuiShenqingEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiXiaohuiShenqingEntity::getDeleteMark, 1));
+        return xiaohuiShenqingMapper.selectList(wrapper).stream().findFirst().orElse(null);
+    }
+
+    private LimsHaocaiChukuShenqingEntity getApprovedChukuShenqingByDanhao(String shenlingDanhao) {
+        LambdaQueryWrapper<LimsHaocaiChukuShenqingEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiChukuShenqingEntity::getShenlingDanhao, trimToEmpty(shenlingDanhao));
+        wrapper.eq(LimsHaocaiChukuShenqingEntity::getFlowState, FLOW_STATE_APPROVED);
+        wrapper.and(w -> w.isNull(LimsHaocaiChukuShenqingEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiChukuShenqingEntity::getDeleteMark, 1));
+        return chukuShenqingMapper.selectList(wrapper).stream().findFirst().orElse(null);
+    }
+
+    private LimsHaocaiChukuShenqingZixiangEntity getChukuShenqingZixiang(String foreignId, String tiaomaBianma) {
+        LambdaQueryWrapper<LimsHaocaiChukuShenqingZixiangEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(LimsHaocaiChukuShenqingZixiangEntity::getForeignId, trimToEmpty(foreignId));
+        wrapper.eq(LimsHaocaiChukuShenqingZixiangEntity::getTiaomaBianma, trimToEmpty(tiaomaBianma));
+        wrapper.and(w -> w.isNull(LimsHaocaiChukuShenqingZixiangEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiChukuShenqingZixiangEntity::getDeleteMark, 1));
+        return chukuShenqingZixiangMapper.selectList(wrapper).stream().findFirst().orElse(null);
+    }
+
+    private LimsHaocaiRukuZixiangEntity getUniqueRukuZixiangByTiaomaBianma(String tiaomaBianma) {
+        List<LimsHaocaiRukuZixiangEntity> list = listRukuZixiangByTiaomaBianma(tiaomaBianma);
+        return list == null || list.isEmpty() ? null : list.get(0);
+    }
+
+    private void updateRukuJieyuShuliang(LimsHaocaiRukuZixiangEntity rukuZixiang,
+                                         String zeroBizStatus) throws DataException {
+        updateRukuJieyuShuliang(rukuZixiang, false, zeroBizStatus);
+    }
+
+    private void updateRukuJieyuShuliang(LimsHaocaiRukuZixiangEntity rukuZixiang,
+                                         boolean increase,
+                                         String zeroBizStatus) throws DataException {
+        BigDecimal changeAmount = parseRequiredDecimal(rukuZixiang.getShengyuliang(),
+                "鑰楁潗鍓╀綑閲忔棤鏁堬細" + rukuZixiang.getTiaomaBianma());
+        updateRukuJieyuShuliang(rukuZixiang, changeAmount, increase, zeroBizStatus);
+    }
+
+    private void updateRukuJieyuShuliang(LimsHaocaiRukuZixiangEntity rukuZixiang,
+                                         BigDecimal changeAmount,
+                                         boolean increase,
+                                         String zeroBizStatus) throws DataException {
+        String id = trimToEmpty(rukuZixiang.getRukuId());
+        if (id.isEmpty()) {
+            throw new DataException("鑰楁潗鍏ュ簱涓昏〃涓婚敭涓嶈兘涓虹┖");
+        }
+        LimsHaocaiRukuEntity ruku = this.getById(id);
+        if (ruku == null || Integer.valueOf(1).equals(ruku.getDeleteMark())) {
+            throw new DataException("鑰楁潗鍏ュ簱涓昏〃涓嶅瓨鍦細" + id);
+        }
+        BigDecimal jieyuShuliang = parseOptionalDecimal(ruku.getJieyuShuliang());
+        if (jieyuShuliang == null) {
+            jieyuShuliang = parseOptionalDecimal(ruku.getRukuShuliang());
+        }
+        if (jieyuShuliang == null) {
+            throw new DataException("鍏ュ簱涓昏〃缁撲綑鏁伴噺鏃犳晥锛�" + id);
+        }
+        BigDecimal changeShuliang = calculateJieyuChangeShuliang(ruku, rukuZixiang, changeAmount);
+        BigDecimal newJieyuShuliang = increase
+                ? jieyuShuliang.add(changeShuliang)
+                : jieyuShuliang.subtract(changeShuliang);
+        if (newJieyuShuliang.compareTo(BigDecimal.ZERO) < 0) {
+            newJieyuShuliang = BigDecimal.ZERO;
+        }
+
+        LimsHaocaiRukuEntity update = new LimsHaocaiRukuEntity();
+        update.setId(id);
+        update.setJieyuShuliang(toPlainString(newJieyuShuliang));
+        update.setBizStatus(resolveRukuBizStatus(ruku, newJieyuShuliang, zeroBizStatus));
+        this.updateById(update);
+    }
+
+    private void updateRukuBizStatus(List<String> rukuIds, String bizStatus) {
+        if (rukuIds == null || rukuIds.isEmpty()) {
+            return;
+        }
+        LimsHaocaiRukuEntity update = new LimsHaocaiRukuEntity();
+        update.setBizStatus(bizStatus);
+        LambdaQueryWrapper<LimsHaocaiRukuEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.in(LimsHaocaiRukuEntity::getId, rukuIds);
+        wrapper.and(w -> w.isNull(LimsHaocaiRukuEntity::getDeleteMark)
+                .or()
+                .ne(LimsHaocaiRukuEntity::getDeleteMark, 1));
+        this.update(update, wrapper);
+    }
+
+    private String resolveRukuBizStatus(LimsHaocaiRukuEntity ruku,
+                                        BigDecimal jieyuShuliang,
+                                        String zeroBizStatus) throws DataException {
+        if (jieyuShuliang.compareTo(BigDecimal.ZERO) <= 0) {
+            return zeroBizStatus;
+        }
+        BigDecimal rukuShuliang = parseRequiredDecimal(ruku.getRukuShuliang(),
+                "鍏ュ簱涓昏〃鍏ュ簱鏁伴噺鏃犳晥锛�" + ruku.getId());
+        if (jieyuShuliang.compareTo(rukuShuliang) >= 0) {
+            return BIZ_STATUS_WEICHUKU;
+        }
+        return BIZ_STATUS_YIKAIPING;
+    }
+
+    private BigDecimal calculateJieyuChangeShuliang(LimsHaocaiRukuEntity ruku,
+                                                     LimsHaocaiRukuZixiangEntity rukuZixiang) throws DataException {
+        BigDecimal changeAmount = parseRequiredDecimal(rukuZixiang.getShengyuliang(),
+                "鑰楁潗鍓╀綑閲忔棤鏁堬細" + rukuZixiang.getTiaomaBianma());
+        return calculateJieyuChangeShuliang(ruku, rukuZixiang, changeAmount);
+    }
+
+    private BigDecimal calculateJieyuChangeShuliang(LimsHaocaiRukuEntity ruku,
+                                                     LimsHaocaiRukuZixiangEntity rukuZixiang,
+                                                     BigDecimal changeAmount) throws DataException {
+        BigDecimal zongliang = parseRequiredDecimal(rukuZixiang.getZongliang(),
+                "鑰楁潗鎬婚噺鏃犳晥锛�" + rukuZixiang.getTiaomaBianma());
+        if (zongliang.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new DataException("鑰楁潗鎬婚噺蹇呴』澶т簬0锛�" + rukuZixiang.getTiaomaBianma());
+        }
+        if (changeAmount.compareTo(BigDecimal.ZERO) < 0) {
+            throw new DataException("鑰楁潗鍙樺寲閲忎笉鑳藉皬浜�0锛�" + rukuZixiang.getTiaomaBianma());
+        }
+        BigDecimal ratio = changeAmount.divide(zongliang, 8, java.math.RoundingMode.HALF_UP);
+        if ("yes".equalsIgnoreCase(trimToEmpty(ruku.getShifouYigeTiaoma()))) {
+            BigDecimal rukuShuliang = parseRequiredDecimal(ruku.getRukuShuliang(),
+                    "鍏ュ簱涓昏〃鍏ュ簱鏁伴噺鏃犳晥锛�" + ruku.getId());
+            return ratio.multiply(rukuShuliang).stripTrailingZeros();
+        }
+        return ratio.stripTrailingZeros();
+    }
+
+    private boolean isChukuAvailableStatus(String bizStatus) {
+        String status = trimToEmpty(bizStatus);
+        return BIZ_STATUS_WEICHUKU.equalsIgnoreCase(status)
+                || BIZ_STATUS_YIKAIPING.equalsIgnoreCase(status);
+    }
+
+    private boolean isBorrowableStatus(String bizStatus) {
+        return isChukuAvailableStatus(bizStatus);
+    }
+
+    private String resolveAvailableBizStatus(BigDecimal shengyuliang,
+                                             LimsHaocaiRukuZixiangEntity rukuZixiang) throws DataException {
+        BigDecimal zongliang = parseRequiredDecimal(rukuZixiang.getZongliang(),
+                "鑰楁潗鎬婚噺鏃犳晥锛�" + rukuZixiang.getTiaomaBianma());
+        if (shengyuliang.compareTo(zongliang) >= 0) {
+            return BIZ_STATUS_WEICHUKU;
+        }
+        return BIZ_STATUS_YIKAIPING;
+    }
+
+    private String resolveYanshouZhuangtai(LimsHaocaiRukuEntity ruku) {
+        return isYanshouEnabled(ruku)
+                ? YANSHOU_STATUS_YIYANSHOU
+                : YANSHOU_STATUS_NA;
+    }
+
+    private boolean isYanshouEnabled(LimsHaocaiRukuEntity ruku) {
+        return ruku != null && "yes".equalsIgnoreCase(trimToEmpty(ruku.getShifouQiyongYanshou()));
+    }
+
+    private void updateRukuYanshouRukuId(LimsHaocaiRukuEntity ruku) throws DataException {
+        String yanshouId = trimToEmpty(ruku.getYanshouId());
+        if (yanshouId.isEmpty()) {
+            throw new DataException("鍚敤楠屾敹鐨勫叆搴撳崟蹇呴』鍏宠仈楠屾敹璁板綍涓婚敭(yanshou_id)锛�" + ruku.getId());
+        }
+
+        LambdaUpdateWrapper<LimsHaocaiRukuYanshouEntity> update = new LambdaUpdateWrapper<>();
+        update.eq(LimsHaocaiRukuYanshouEntity::getId, yanshouId)
+                .and(w -> w.isNull(LimsHaocaiRukuYanshouEntity::getDeleteMark)
+                        .or().ne(LimsHaocaiRukuYanshouEntity::getDeleteMark, 1))
+                .set(LimsHaocaiRukuYanshouEntity::getRukuId, ruku.getId());
+        if (rukuYanshouMapper.update(null, update) != 1) {
+            throw new DataException("鑰楁潗鍏ュ簱楠屾敹璁板綍涓嶅瓨鍦ㄦ垨宸插垹闄わ細" + yanshouId);
+        }
+    }
+
+    private String yanshouShenqingStatusLabel(String status) {
+        String value = trimToEmpty(status);
+        if (YANSHOU_SHENQING_STATUS_DAIYANSHOU.equals(value)) {
+            return "寰呴獙鏀�";
+        }
+        if (YANSHOU_SHENQING_STATUS_YANSHOUZHONG.equals(value)) {
+            return "楠屾敹涓�";
+        }
+        return value.isEmpty() ? "鏈缃�" : value;
+    }
+
+    /**
+     * 鍙栨潯鐮佸彿銆侭illRuleApi 閰嶄簡闈欐�� fallback锛圠009锛夛細system 鏈嶅姟涓嶅彲鐢ㄦ椂
+     * 闈欓粯杩斿洖绌� ActionResult 鑰屼笉鎶涘紓甯革紝蹇呴』鏄惧紡鏍¢獙鍙栧彿缁撴灉锛岀┖鍙风珛鍗冲け璐ュ洖婊氥��
+     */
+    private String nextTiaomaBianma() throws DataException {
+        ActionResult<String> result = billRuleApi.getBillNumber(TIAOMA_BILL_RULE);
+        String tiaoma = result == null ? null : result.getData();
+        if (StringUtil.isEmpty(tiaoma)) {
+            throw new DataException("鏉$爜鍙栧彿澶辫触锛氬崟鎹鍒橻" + TIAOMA_BILL_RULE + "]涓嶅彲鐢紝璇风‘璁よ鍒欏凡閰嶇疆涓旂郴缁熸湇鍔″湪绾�");
+        }
+        return tiaoma;
+    }
+
+    private BigDecimal parseShuliang(String rukuShuliang) throws DataException {
+        try {
+            BigDecimal shuliang = new BigDecimal(trimToEmpty(rukuShuliang));
+            if (shuliang.compareTo(BigDecimal.ZERO) <= 0) {
+                throw new DataException("鍏ュ簱鏁伴噺蹇呴』澶т簬0锛�" + rukuShuliang);
+            }
+            return shuliang;
+        } catch (NumberFormatException e) {
+            throw new DataException("鍏ュ簱鏁伴噺鏃犳晥锛�" + rukuShuliang);
+        }
+    }
+
+    private int calculateCompleteTiaomaCount(BigDecimal shuliang, String originalQuantity)
+            throws DataException {
+        try {
+            return shuliang.setScale(0, RoundingMode.DOWN).intValueExact();
+        } catch (ArithmeticException e) {
+            throw new DataException("鍏ュ簱鏁伴噺杩囧ぇ锛�" + originalQuantity);
+        }
+    }
+
+    private BigDecimal parseRongliang(String rongliang) throws DataException {
+        try {
+            return new BigDecimal(trimToEmpty(rongliang));
+        } catch (NumberFormatException e) {
+            throw new DataException("瀹归噺鏃犳晥锛�" + rongliang);
+        }
+    }
+
+    private BigDecimal parseRequiredDecimal(String value, String errorMessage) throws DataException {
+        BigDecimal decimal = parseOptionalDecimal(value);
+        if (decimal == null) {
+            throw new DataException(errorMessage);
+        }
+        return decimal;
+    }
+
+    private BigDecimal parseOptionalDecimal(String value) {
+        String text = trimToEmpty(value);
+        if (text.isEmpty()) {
+            return null;
+        }
+        try {
+            return new BigDecimal(text);
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    private List<String> normalizeIds(List<String> ids) {
+        List<String> result = new ArrayList<>();
+        if (ids == null) {
+            return result;
+        }
+        for (String id : ids) {
+            String value = trimToEmpty(id);
+            if (!value.isEmpty() && !result.contains(value)) {
+                result.add(value);
+            }
+        }
+        return result;
+    }
+
+    private String trimToEmpty(String value) {
+        return value == null ? "" : value.trim();
+    }
+
+    private String firstNotBlank(String first, String second) {
+        String value = trimToEmpty(first);
+        return value.isEmpty() ? second : value;
+    }
+
+    private String toPlainString(BigDecimal value) {
+        return value.stripTrailingZeros().toPlainString();
+    }
+}

--
Gitblit v1.8.0