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-controller/src/main/java/jnpf/limsController/LimsLiuyangController.java | 1173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1,173 insertions(+), 0 deletions(-)

diff --git a/jnpf-lims/jnpf-lims-controller/src/main/java/jnpf/limsController/LimsLiuyangController.java b/jnpf-lims/jnpf-lims-controller/src/main/java/jnpf/limsController/LimsLiuyangController.java
new file mode 100644
index 0000000..54fc6ca
--- /dev/null
+++ b/jnpf-lims/jnpf-lims-controller/src/main/java/jnpf/limsController/LimsLiuyangController.java
@@ -0,0 +1,1173 @@
+package jnpf.limsController;
+
+import jnpf.audit.sdk.annotation.AuditLog;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jnpf.limsEntity.LimsLiuyangBiangengShenqingEntity;
+import jnpf.limsEntity.LimsLiuyangJihuaBiangengShenqingEntity;
+import jnpf.limsEntity.LimsLiuyangJihuaBiangengShenqingZixiangEntity;
+import jnpf.limsEntity.LimsLiuyangJihuaCalculateParam;
+import jnpf.limsEntity.LimsLiuyangJihuaCalculateResult;
+import jnpf.limsEntity.LimsLiuyangJihuaBiangengCalculateParam;
+import jnpf.limsEntity.LimsLiuyangJihuaEntity;
+import jnpf.limsEntity.LimsLiuyangJihuaZixiangEntity;
+import jnpf.limsEntity.LimsLiuyangJiluEntity;
+import jnpf.limsEntity.LiuyangStatus;
+import jnpf.limsEntity.LiuyangWendingxingZhouqiZixiangEntity;
+import jnpf.limsEntity.LimsQingyandanEntity;
+import jnpf.limsService.LiuyangWendingxingZhouqiZixiangService;
+import jnpf.limsService.LimsLiuyangJihuaService;
+import jnpf.limsService.LimsLiuyangJihuaZixiangService;
+import jnpf.limsService.LimsLiuyangBiangengShenqingService;
+import jnpf.limsService.LimsLiuyangJihuaBiangengShenqingService;
+import jnpf.limsService.LimsLiuyangJihuaBiangengShenqingZixiangService;
+import jnpf.limsService.LimsLiuyangJiluService;
+import jnpf.limsService.LimsQingyandanService;
+import jnpf.util.R;
+import jnpf.util.RandomUtil;
+import jnpf.util.UserProvider;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.sql.DataSource;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Tag(name = "lims鐣欐牱鎺ュ彛", description = "鐣欐牱璁板綍娴佺▼鎿嶄綔")
+@RestController
+@RequestMapping("/lims/biz")
+public class LimsLiuyangController {
+
+    private static final String BIANGENG_ZIXIANG_TABLE = "lims_liuyang_jihua_biangeng_shenqing_zixiang";
+    private static final String LIUYANG_CHUKU_SHENQING_TABLE = "lims_liuyang_chuku_shenqing";
+    private static final String LIUYANG_CHUKU_SHENQING_ZIXIANG_TABLE = "lims_liuyang_chuku_shenqing_zixiang";
+    private static final String LIUYANG_CHUKU_TABLE = "lims_liuyang_chuku";
+    private static final String LIUYANG_XIAOHUI_SHENQING_TABLE = "lims_liuyang_xiaohui_shenqing";
+    private static final String LIUYANG_XIAOHUI_SHENQING_ZIXIANG_TABLE = "lims_liuyang_xiaohui_shenqing_zixiang";
+    private static final String BIZ_STATUS_DESTROYED = "destroyed";
+    private static final String BIZ_STATUS_DISABLED = "disabled";
+
+    private static final List<String> DEFAULT_BIANGENG_ZIXIANG_FIELDS = Arrays.asList(
+            "zhouqi",
+            "zhouqi_danwei",
+            "jihua_riqi",
+            "shifou_yiguancha",
+            "guancha_riqi",
+            "guancha_ren",
+            "guancha_shuliang",
+            "guancha_qingkuang",
+            "guancha_jieguo",
+            "beizhu"
+    );
+
+    @Autowired
+    private LimsLiuyangJiluService limsLiuyangJiluService;
+
+    @Autowired
+    private LimsQingyandanService limsQingyandanService;
+
+    @Autowired
+    private LiuyangWendingxingZhouqiZixiangService liuyangWendingxingZhouqiZixiangService;
+
+    @Autowired
+    private LimsLiuyangJihuaService limsLiuyangJihuaService;
+
+    @Autowired
+    private LimsLiuyangJihuaZixiangService limsLiuyangJihuaZixiangService;
+
+    @Autowired
+    private LimsLiuyangBiangengShenqingService limsLiuyangBiangengShenqingService;
+
+    @Autowired
+    private LimsLiuyangJihuaBiangengShenqingService limsLiuyangJihuaBiangengShenqingService;
+
+    @Autowired
+    private LimsLiuyangJihuaBiangengShenqingZixiangService limsLiuyangJihuaBiangengShenqingZixiangService;
+
+    @Autowired
+    private DataSource dataSource;
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    /**
+     * 鐣欐牱鎿嶄綔锛�
+     * submit -> 寰呭鏍革紝骞舵妸璇烽獙鍗� liuyang_jilu_id 鍏宠仈鍒板綋鍓嶇暀鏍疯褰曪紱
+     * reject -> 宸查��鍥烇紝骞舵竻绌鸿楠屽崟 liuyang_jilu_id锛�
+     * accept -> 宸叉帴鏀讹紝骞跺悓姝ュ叆搴撴暟閲�/鍗曚綅涓虹暀鏍风幇瀛橀噺锛�
+     * delete -> 鎸� JNPF 琛ㄥ崟瑙勮寖杞垹闄ょ暀鏍疯褰曘��
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Operation(summary = "鐣欐牱鎿嶄綔锛堟彁浜�/鎷掔粷/鍚屾剰锛�")
+    @PostMapping("/liuyang/action")
+    @AuditLog(action = "LIUYANG_ACTION", label = "鐣欐牱鎿嶄綔", bizType = "LIUYANG",
+            bizCodeExpr = "#result.success ? (#params['id'] ?: 'LIUYANG_ACTION') : ''", targetTable = "lims_liuyang_jilu",
+            targetIdExpr = "#params['id']", extraExpr = "{'action': #params['action']}",
+            diff = true, entityClass = LimsLiuyangJiluEntity.class)
+    public R<String> liuyangAction(@RequestBody Map<String, String> params) {
+        String action = params.get("action");
+        String id = params.get("id");
+        String flowId = firstNotBlank(params.get("flow_id"), params.get("flowId"));
+        String taskId = firstNotBlank(params.get("task_id"), params.get("taskId"));
+        String bizSign = params.get("biz_sign");
+
+        if (action == null || action.isEmpty()) {
+            return R.error("缂哄皯 action");
+        }
+        if ((id == null || id.isEmpty()) && (flowId == null || flowId.isEmpty())) {
+            return R.error("缂哄皯 flow_id");
+        }
+
+        LimsLiuyangJiluEntity liuyangJilu = findLiuyangJilu(id, flowId, taskId);
+        if (liuyangJilu == null) {
+            return R.error("鐣欐牱璁板綍鏁版嵁涓嶅瓨鍦�");
+        }
+
+        switch (action) {
+            case "submit":
+                updateLiuyangStatus(liuyangJilu, LiuyangStatus.PENDING_AUDIT, bizSign);
+                updateQingyandanLiuyangJiluId(liuyangJilu, liuyangJilu.getId());
+                return R.success("鎻愪氦鎴愬姛");
+            case "reject":
+                updateLiuyangStatus(liuyangJilu, LiuyangStatus.REJECTED, bizSign);
+                updateQingyandanLiuyangJiluId(liuyangJilu, null);
+                return R.success("鎷掔粷鎴愬姛");
+            case "accept":
+                receiveLiuyangJilu(liuyangJilu, bizSign);
+                return R.success("鍚屾剰鎴愬姛");
+            case "delete":
+                softDeleteLiuyangJilu(liuyangJilu, bizSign);
+                updateQingyandanLiuyangJiluId(liuyangJilu, null);
+                return R.success("鍒犻櫎鎴愬姛");
+            default:
+                return R.error("涓嶆敮鎸佺殑鎿嶄綔: " + action);
+        }
+    }
+
+    /**
+     * 鐣欐牱閿�姣佹祦绋嬫搷浣滐細
+     * submit -> 閿�姣佸崟鍙峰洖鍐欏埌鐣欐牱璁板綍锛�
+     * reject -> 娓呯┖鐣欐牱璁板綍閿�姣佸崟鍙凤紱
+     * accept -> shifou_pizhun=yes 鏃剁疆涓哄凡閿�姣佸苟绂佺敤鍏宠仈瑙傚療璁″垝鍙婂瓙椤癸紝shifou_pizhun=no 鏃舵竻绌洪攢姣佸崟鍙枫��
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Operation(summary = "鐣欐牱閿�姣佹搷浣滐紙鎻愪氦/鎷掔粷/鍚屾剰锛�")
+    @PostMapping("/liuyang/xiaohui/action")
+    @AuditLog(action = "DESTROY_ACTION", label = "鐣欐牱閿�姣佹搷浣�", bizType = "LIUYANG",
+            bizCodeExpr = "#result.success ? (#params['id'] ?: 'LIUYANG_DESTROY') : ''", targetTable = "lims_liuyang_jilu",
+            targetIdExpr = "#params['id']", extraExpr = "{'action': #params['action']}",
+            diff = true, entityClass = LimsLiuyangJiluEntity.class)
+    public R<String> liuyangXiaohuiAction(@RequestBody Map<String, String> params) {
+        if (params == null) {
+            return R.error("璇锋眰鍙傛暟涓嶈兘涓虹┖");
+        }
+        String flowId = firstNotBlank(params.get("flow_id"), params.get("flowId"));
+        String taskId = firstNotBlank(params.get("task_id"), params.get("taskId"));
+        String action = params.get("action");
+        String shifouPizhun = firstNotBlank(params.get("shifou_pizhun"), params.get("shifouPizhun"));
+
+        if (isBlank(flowId)) {
+            return R.error("娴佺▼id(flow_id)涓嶈兘涓虹┖");
+        }
+        if (isBlank(taskId)) {
+            return R.error("娴佺▼浠诲姟id(task_id)涓嶈兘涓虹┖");
+        }
+        if (isBlank(action)) {
+            return R.error("缂哄皯 action");
+        }
+
+        Map<String, Object> shenqing = findLiuyangXiaohuiShenqing(flowId, taskId);
+        if (shenqing == null || shenqing.isEmpty()) {
+            return R.error("鐣欐牱閿�姣佺敵璇锋暟鎹笉瀛樺湪");
+        }
+        String shenqingId = getString(shenqing, "f_id");
+        String xiaohuiDanhao = getString(shenqing, "xiaohui_danhao");
+        List<String> liuyangJiluIds = listLiuyangXiaohuiJiluIds(shenqingId);
+        if (liuyangJiluIds.isEmpty()) {
+            return R.error("鐣欐牱閿�姣佺敵璇峰瓙椤逛笉鑳戒负绌�");
+        }
+
+        switch (action) {
+            case "submit":
+                if (isBlank(xiaohuiDanhao)) {
+                    return R.error("閿�姣佸崟鍙蜂笉鑳戒负绌�");
+                }
+                updateLiuyangXiaohuiDanhao(liuyangJiluIds, xiaohuiDanhao);
+                return R.success("鎻愪氦鎴愬姛");
+            case "reject":
+                updateLiuyangXiaohuiDanhao(liuyangJiluIds, null);
+                return R.success("鎷掔粷鎴愬姛");
+            case "accept":
+                if ("yes".equals(shifouPizhun)) {
+                    acceptLiuyangXiaohui(liuyangJiluIds);
+                    return R.success("鍚屾剰鎴愬姛");
+                }
+                if ("no".equals(shifouPizhun)) {
+                    updateLiuyangXiaohuiDanhao(liuyangJiluIds, null);
+                    return R.success("鎷掔粷鎴愬姛");
+                }
+                return R.error("鏄惁鎵瑰噯(shifou_pizhun)涓嶈兘涓虹┖锛屼笖蹇呴』涓� yes 鎴� no");
+            default:
+                return R.error("涓嶆敮鎸佺殑鎿嶄綔: " + action);
+        }
+    }
+
+    /**
+     * 璁$畻鐣欐牱璁″垝瀛愰」鍛ㄦ湡鍒楄〃銆�
+     */
+    @Operation(summary = "璁$畻鐣欐牱璁″垝鍛ㄦ湡")
+    @PostMapping("/liuyang/jihua/calculate")
+    public R<List<LimsLiuyangJihuaCalculateResult>> calculateLiuyangJihua(@RequestBody LimsLiuyangJihuaCalculateParam param) {
+        if (param == null || isBlank(param.getLiuyangJiluId())) {
+            return R.error("鐣欐牱璁板綍涓婚敭涓嶈兘涓虹┖");
+        }
+        if (isBlank(param.getLiuyangZhouqiId())) {
+            return R.error("鐣欐牱鍛ㄦ湡涓婚敭涓嶈兘涓虹┖");
+        }
+
+        LimsLiuyangJiluEntity liuyangJilu = limsLiuyangJiluService.getById(param.getLiuyangJiluId());
+        if (liuyangJilu == null || Integer.valueOf(1).equals(liuyangJilu.getDeleteMark())) {
+            return R.error("鐣欐牱璁板綍鏁版嵁涓嶅瓨鍦�");
+        }
+        if (liuyangJilu.getShouyangRiqi() == null) {
+            return R.error("鐣欐牱璁板綍鏀舵牱鏃ユ湡涓嶈兘涓虹┖");
+        }
+
+        List<LiuyangWendingxingZhouqiZixiangEntity> zhouqiList = listWendingxingZhouqiZixiang(param.getLiuyangZhouqiId());
+        if (zhouqiList.isEmpty()) {
+            return R.error("绋冲畾鎬у懆鏈熷瓙椤逛笉鑳戒负绌�");
+        }
+
+        try {
+            List<LimsLiuyangJihuaCalculateResult> jihuaList = zhouqiList.stream()
+                    .map(row -> buildJihuaZixiang(row, liuyangJilu.getShouyangRiqi()))
+                    .collect(Collectors.toList());
+            return R.success("璁$畻鎴愬姛", jihuaList);
+        } catch (IllegalArgumentException e) {
+            return R.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 璁″垝鍙樻洿鏃讹紝鎸夋柊鐨勭暀鏍锋棩鏈熼噸鏂拌绠楁湭瑙傚療璁″垝锛屽苟淇濈暀宸茶瀵熺殑鍘熻鍒掋��
+     */
+    @Operation(summary = "璁$畻鐣欐牱璁″垝鍙樻洿瀛愰」")
+    @PostMapping("/liuyang/jihua/biangeng/calculate")
+    public R<List<Map<String, Object>>> calculateLiuyangJihuaBiangeng(@RequestBody LimsLiuyangJihuaBiangengCalculateParam param) {
+        if (param == null || isBlank(param.getLiuyangJihuaId())) {
+            return R.error("鐣欐牱瑙傚療璁″垝涓婚敭涓嶈兘涓虹┖");
+        }
+        if (isBlank(param.getLiuyangZhouqiId())) {
+            return R.error("鐣欐牱鍛ㄦ湡涓婚敭涓嶈兘涓虹┖");
+        }
+        if (param.getLiuyangRiqi() == null) {
+            return R.error("鐣欐牱鏃ユ湡涓嶈兘涓虹┖");
+        }
+
+        List<LiuyangWendingxingZhouqiZixiangEntity> zhouqiList = listWendingxingZhouqiZixiang(param.getLiuyangZhouqiId());
+        if (zhouqiList.isEmpty()) {
+            return R.error("绋冲畾鎬у懆鏈熷瓙椤逛笉鑳戒负绌�");
+        }
+
+        try {
+            List<String> responseFields = getBiangengZixiangBusinessFields();
+            List<Map<String, Object>> result = new ArrayList<>();
+
+            List<LimsLiuyangJihuaZixiangEntity> observedList = listObservedJihuaZixiang(param.getLiuyangJihuaId());
+            Set<String> observedZhouqiKeys = observedList.stream()
+                    .map(row -> buildZhouqiKey(row.getZhouqi(), row.getZhouqiDanwei()))
+                    .collect(Collectors.toSet());
+            observedList.stream()
+                    .map(row -> buildBiangengRow(row, responseFields))
+                    .forEach(result::add);
+
+            zhouqiList.stream()
+                    .filter(row -> !observedZhouqiKeys.contains(buildZhouqiKey(row.getZhouqi(), row.getZhouqiDanwei())))
+                    .map(row -> buildBiangengRow(row, param.getLiuyangRiqi(), responseFields))
+                    .forEach(result::add);
+
+            return R.success("璁$畻鎴愬姛", result);
+        } catch (IllegalArgumentException e) {
+            return R.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 鎻愪氦鐣欐牱瑙傚療璁″垝锛氬惎鐢ㄨ鍒掞紝骞跺叧鑱斿洖鐣欐牱璁板綍銆�
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Operation(summary = "鎻愪氦鐣欐牱瑙傚療璁″垝")
+    @PostMapping("/liuyang/jihua/submit")
+    @AuditLog(action = "PLAN_SUBMIT", label = "鎻愪氦鐣欐牱瑙傚療璁″垝", bizType = "LIUYANG",
+            bizCodeExpr = "#result.success ? 'LIUYANG_PLAN' : ''", targetTable = "lims_liuyang_jihua",
+            targetIdExpr = "#formData['id'] ?: #formData['f_id']", diff = true,
+            entityClass = LimsLiuyangJihuaEntity.class)
+    public R<String> submitLiuyangJihua(@RequestBody Map<String, Object> formData) {
+        String jihuaId = getFormId(formData);
+        if (isBlank(jihuaId)) {
+            return R.error("鐣欐牱瑙傚療璁″垝涓婚敭涓嶈兘涓虹┖");
+        }
+
+        LimsLiuyangJihuaEntity jihua = limsLiuyangJihuaService.getById(jihuaId);
+        if (jihua == null || Integer.valueOf(1).equals(jihua.getDeleteMark())) {
+            return R.error("鐣欐牱瑙傚療璁″垝鏁版嵁涓嶅瓨鍦�");
+        }
+        if (isBlank(jihua.getLiuyangJiluId())) {
+            return R.error("鐣欐牱瑙傚療璁″垝鏈叧鑱旂暀鏍疯褰�");
+        }
+
+        LimsLiuyangJihuaEntity jihuaUpdate = new LimsLiuyangJihuaEntity();
+        jihuaUpdate.setId(jihua.getId());
+        jihuaUpdate.setBizStatus("enabled");
+        limsLiuyangJihuaService.updateById(jihuaUpdate);
+        jihua.setBizStatus("enabled");
+
+        syncJihuaInfoToZixiang(jihua);
+
+        LimsLiuyangJiluEntity jiluUpdate = new LimsLiuyangJiluEntity();
+        jiluUpdate.setId(jihua.getLiuyangJiluId());
+        jiluUpdate.setLiuyangJihuaId(jihua.getId());
+        limsLiuyangJiluService.updateById(jiluUpdate);
+
+        return R.success("鎻愪氦鎴愬姛");
+    }
+
+    /**
+     * 鐣欐牱瑙傚療璁″垝鍙樻洿锛氬皢鍙樻洿鐢宠鍚屾鍒板疄闄呯暀鏍疯瀵熻鍒掋��
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Operation(summary = "鐣欐牱瑙傚療璁″垝鍙樻洿")
+    @PostMapping("/liuyang/jihua/biangeng/action")
+    @AuditLog(action = "PLAN_CHANGE", label = "鐣欐牱瑙傚療璁″垝鍙樻洿", bizType = "LIUYANG",
+            bizCodeExpr = "#result.success ? (#params['id'] ?: 'LIUYANG_PLAN_CHANGE') : ''", targetTable = "lims_liuyang_jihua",
+            targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).liuyangJihuaByChangeFlow(" +
+                    "#params['flow_id'] ?: #params['flowId'], #params['task_id'] ?: #params['taskId'])",
+            extraExpr = "{'action': #params['action']}", diff = true,
+            entityClass = LimsLiuyangJihuaEntity.class)
+    public R<String> liuyangJihuaBiangengAction(@RequestBody Map<String, String> params) {
+        if (params == null) {
+            return R.error("璇锋眰鍙傛暟涓嶈兘涓虹┖");
+        }
+        String flowId = firstNotBlank(params.get("flow_id"), params.get("flowId"));
+        String taskId = firstNotBlank(params.get("task_id"), params.get("taskId"));
+
+        if (isBlank(flowId)) {
+            return R.error("娴佺▼id(flow_id)涓嶈兘涓虹┖");
+        }
+        if (isBlank(taskId)) {
+            return R.error("娴佺▼浠诲姟id(task_id)涓嶈兘涓虹┖");
+        }
+
+        LimsLiuyangJihuaBiangengShenqingEntity shenqing = findJihuaBiangengShenqing(flowId, taskId);
+        if (shenqing == null) {
+            return R.error("鐣欐牱瑙傚療璁″垝鍙樻洿鐢宠鏁版嵁涓嶅瓨鍦�");
+        }
+        if (isBlank(shenqing.getLiuyangJihuaId())) {
+            return R.error("鍙樻洿鐢宠鏈叧鑱旂暀鏍疯瀵熻鍒�");
+        }
+
+        LimsLiuyangJihuaEntity jihua = limsLiuyangJihuaService.getById(shenqing.getLiuyangJihuaId());
+        if (jihua == null || Integer.valueOf(1).equals(jihua.getDeleteMark())) {
+            return R.error("鐣欐牱瑙傚療璁″垝鏁版嵁涓嶅瓨鍦�");
+        }
+
+        updateJihuaByBiangeng(shenqing);
+        softDeleteUnobservedJihuaZixiang(shenqing.getLiuyangJihuaId());
+        syncBiangengZixiangToJihuaZixiang(jihua, shenqing);
+
+        return R.success("鍙樻洿鎴愬姛");
+    }
+
+    /**
+     * 鐣欐牱鏁伴噺鍙樻洿锛氬皢鍙樻洿鐢宠鐨勫叆搴撴暟閲�/鍗曚綅鍚屾鍒扮暀鏍疯褰曘��
+     * 浠呭綋鐣欐牱璁板綍鏈嚭搴擄紙鍏ュ簱鏁伴噺 == 鐣欐牱鐜板瓨閲忥級鏃跺厑璁稿彉鏇淬��
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Operation(summary = "鐣欐牱鏁伴噺鍙樻洿")
+    @PostMapping("/liuyang/biangeng/action")
+    @AuditLog(action = "QUANTITY_CHANGE", label = "鐣欐牱鏁伴噺鍙樻洿", bizType = "LIUYANG",
+            bizCodeExpr = "#result.success ? #params['liuyang_jilu_id'] : ''",
+            targetTable = "lims_liuyang_jilu", targetIdExpr = "#params['liuyang_jilu_id']",
+            diff = true, entityClass = LimsLiuyangJiluEntity.class)
+    public R<String> liuyangBiangengAction(@RequestBody Map<String, String> params) {
+        String liuyangJiluId = params.get("liuyang_jilu_id");
+        String biangengShenqingId = params.get("biangeng_shenqing_id");
+
+        if (isBlank(liuyangJiluId)) {
+            return R.error("鐣欐牱璁板綍涓婚敭涓嶈兘涓虹┖");
+        }
+        if (isBlank(biangengShenqingId)) {
+            return R.error("鍙樻洿鐢宠涓婚敭涓嶈兘涓虹┖");
+        }
+
+        LimsLiuyangJiluEntity jilu = findById(liuyangJiluId);
+        if (jilu == null) {
+            return R.error("鐣欐牱璁板綍鏁版嵁涓嶅瓨鍦�");
+        }
+
+        LimsLiuyangBiangengShenqingEntity shenqing = findBiangengShenqing(biangengShenqingId);
+        if (shenqing == null) {
+            return R.error("鍙樻洿鐢宠鏁版嵁涓嶅瓨鍦�");
+        }
+
+        if (!equalsNullable(jilu.getRukuShuliang(), jilu.getLiuyangXiancunliang())) {
+            return R.error("璇ョ暀鏍锋牱鍝佸凡鍑哄簱锛屾棤娉曞彉鏇存暟閲�");
+        }
+
+        LimsLiuyangJiluEntity jiluUpdate = new LimsLiuyangJiluEntity();
+        jiluUpdate.setId(jilu.getId());
+        jiluUpdate.setRukuShuliang(shenqing.getRukuShuliang());
+        jiluUpdate.setLiuyangXiancunliang(shenqing.getRukuShuliang());
+        jiluUpdate.setDanwei(shenqing.getDanwei());
+        jiluUpdate.setLiuyangXiancunliangDanwei(shenqing.getDanwei());
+        limsLiuyangJiluService.updateById(jiluUpdate);
+
+        syncBiangengToJihua(liuyangJiluId, shenqing);
+
+        return R.success("鍙樻洿鎴愬姛");
+    }
+
+    /**
+     * 鐣欐牱鍑哄簱楠岃瘉锛氬鎵瑰墠鏍¢獙鐢宠鍑哄簱鏁伴噺涓嶈兘澶т簬鐣欐牱鐜板瓨閲忋��
+     */
+    @Operation(summary = "鐣欐牱鍑哄簱楠岃瘉")
+    @PostMapping("/liuyang/chuku/validate")
+    public R<String> validateLiuyangChuku(@RequestBody Map<String, String> params) {
+        if (params == null) {
+            return R.error("璇锋眰鍙傛暟涓嶈兘涓虹┖");
+        }
+        String flowId = params.get("flow_id");
+        String taskId = params.get("task_id");
+        if (isBlank(flowId)) {
+            return R.error("娴佺▼id(flow_id)涓嶈兘涓虹┖");
+        }
+        if (isBlank(taskId)) {
+            return R.error("娴佺▼浠诲姟id(task_id)涓嶈兘涓虹┖");
+        }
+
+        try {
+            String shenqingId = findLiuyangChukuShenqingId(flowId, taskId);
+            if (isBlank(shenqingId)) {
+                return R.error("鐣欐牱鍑哄簱鐢宠鏁版嵁涓嶅瓨鍦�");
+            }
+
+            List<Map<String, String>> zixiangList = listLiuyangChukuShenqingZixiang(shenqingId);
+            for (Map<String, String> zixiang : zixiangList) {
+                String liuyangJiluId = zixiang.get("liuyang_jilu_id");
+                LimsLiuyangJiluEntity jilu = isBlank(liuyangJiluId) ? null : findById(liuyangJiluId);
+                if (jilu == null) {
+                    return R.error(buildChukuStockErrorMessage(zixiang));
+                }
+
+                BigDecimal chukuShuliang = parseDecimal(zixiang.get("chuku_shuliang"));
+                BigDecimal xiancunliang = parseDecimal(jilu.getLiuyangXiancunliang());
+                if (chukuShuliang == null || xiancunliang == null || chukuShuliang.compareTo(xiancunliang) > 0) {
+                    return R.error(buildChukuStockErrorMessage(zixiang, jilu));
+                }
+            }
+            return R.success("楠岃瘉閫氳繃");
+        } catch (SQLException e) {
+            log.error("鐣欐牱鍑哄簱楠岃瘉澶辫触锛歠lowId={}, taskId={}", flowId, taskId, e);
+            return R.error("鐣欐牱鍑哄簱楠岃瘉澶辫触");
+        }
+    }
+
+    /**
+     * 鐣欐牱鍑哄簱锛氭寜鐢宠瀛愰」鎵e噺鐣欐牱鐜板瓨閲忥紝鍚屾瑙傚療璁″垝鐜板瓨閲忥紝骞跺啓鍑哄簱璁板綍銆�
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Operation(summary = "鐣欐牱鍑哄簱")
+    @PostMapping("/liuyang/chuku/action")
+    @AuditLog(action = "OUTBOUND", label = "鐣欐牱鍑哄簱", bizType = "LIUYANG",
+            bizCodeExpr = "#result.success ? #params['liuyang_jilu_id'] : ''",
+            targetTable = "lims_liuyang_jilu", targetIdExpr = "#params['liuyang_jilu_id']",
+            diff = true, entityClass = LimsLiuyangJiluEntity.class)
+    public R<String> liuyangChukuAction(@RequestBody Map<String, String> params) {
+        if (params == null) {
+            return R.error("璇锋眰鍙傛暟涓嶈兘涓虹┖");
+        }
+        String flowId = params.get("flow_id");
+        String taskId = params.get("task_id");
+        if (isBlank(flowId)) {
+            return R.error("娴佺▼id(flow_id)涓嶈兘涓虹┖");
+        }
+        if (isBlank(taskId)) {
+            return R.error("娴佺▼浠诲姟id(task_id)涓嶈兘涓虹┖");
+        }
+
+        Map<String, Object> shenqing = findLiuyangChukuShenqing(flowId, taskId);
+        if (shenqing == null || shenqing.isEmpty()) {
+            return R.error("鐣欐牱鍑哄簱鐢宠鏁版嵁涓嶅瓨鍦�");
+        }
+        String shenqingId = getString(shenqing, "f_id");
+        List<Map<String, Object>> zixiangList = listLiuyangChukuShenqingZixiangRows(shenqingId);
+        if (zixiangList.isEmpty()) {
+            return R.error("鐣欐牱鍑哄簱鐢宠瀛愰」涓嶈兘涓虹┖");
+        }
+
+        for (Map<String, Object> zixiang : zixiangList) {
+            String liuyangJiluId = getString(zixiang, "liuyang_jilu_id");
+            LimsLiuyangJiluEntity jilu = isBlank(liuyangJiluId) ? null : findById(liuyangJiluId);
+            if (jilu == null) {
+                return R.error(buildChukuStockErrorMessage(zixiang));
+            }
+
+            BigDecimal chukuShuliang = parseDecimal(getString(zixiang, "chuku_shuliang"));
+            BigDecimal oldXiancunliang = parseDecimal(jilu.getLiuyangXiancunliang());
+            if (chukuShuliang == null || oldXiancunliang == null || chukuShuliang.compareTo(oldXiancunliang) > 0) {
+                return R.error(buildChukuStockErrorMessage(zixiang, jilu));
+            }
+
+            BigDecimal newXiancunliang = oldXiancunliang.subtract(chukuShuliang);
+            String newXiancunliangText = toPlainString(newXiancunliang);
+
+            LimsLiuyangJiluEntity jiluUpdate = new LimsLiuyangJiluEntity();
+            jiluUpdate.setId(jilu.getId());
+            jiluUpdate.setLiuyangXiancunliang(newXiancunliangText);
+            limsLiuyangJiluService.updateById(jiluUpdate);
+
+            syncChukuLiuyangXiancunliangToJihua(jilu.getId(), newXiancunliangText);
+            insertLiuyangChukuRecord(shenqing, zixiang, jilu, oldXiancunliang);
+        }
+
+        return R.success("鍑哄簱鎴愬姛");
+    }
+
+    private Map<String, Object> findLiuyangChukuShenqing(String flowId, String taskId) {
+        String sql = "select * from " + LIUYANG_CHUKU_SHENQING_TABLE
+                + " where f_flow_id = ? and f_flow_task_id = ?"
+                + " and (f_delete_mark is null or f_delete_mark <> 1)"
+                + " limit 1";
+        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, flowId, taskId);
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    private List<Map<String, Object>> listLiuyangChukuShenqingZixiangRows(String shenqingId) {
+        String sql = "select * from " + LIUYANG_CHUKU_SHENQING_ZIXIANG_TABLE
+                + " where f_foreign_id = ?"
+                + " and (f_delete_mark is null or f_delete_mark <> 1)";
+        return jdbcTemplate.queryForList(sql, shenqingId);
+    }
+
+    private void syncChukuLiuyangXiancunliangToJihua(String liuyangJiluId, String liuyangXiancunliang) {
+        LambdaUpdateWrapper<LimsLiuyangJihuaEntity> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(LimsLiuyangJihuaEntity::getLiuyangJiluId, liuyangJiluId)
+                .and(w -> w.isNull(LimsLiuyangJihuaEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaEntity::getDeleteMark, 1))
+                .set(LimsLiuyangJihuaEntity::getLiuyangXiancunliang, liuyangXiancunliang);
+        limsLiuyangJihuaService.update(wrapper);
+    }
+
+    private void insertLiuyangChukuRecord(Map<String, Object> shenqing,
+                                          Map<String, Object> zixiang,
+                                          LimsLiuyangJiluEntity jilu,
+                                          BigDecimal oldXiancunliang) {
+        String sql = "insert into " + LIUYANG_CHUKU_TABLE
+                + " (f_id, f_foreign_id, f_tenant_id, chuku_shenqing_id,"
+                + " yangpin_mingcheng, yangpin_bianma, guige, pihao,"
+                + " liuyang_xiancunliang, chuku_shuliang, danwei, yongtu,"
+                + " shenqing_ren, shenqing_riqi, fafang_ren, shenhe_ren, shenhe_riqi, fafang_riqi)"
+                + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+        String shenqingId = getString(shenqing, "f_id");
+        jdbcTemplate.update(sql,
+                RandomUtil.uuId(),
+                jilu.getId(),
+                firstNotBlank(getString(zixiang, "f_tenant_id"), getString(shenqing, "f_tenant_id")),
+                shenqingId,
+                firstNotBlank(getString(zixiang, "yangpin_mingcheng"), jilu.getYangpinMingcheng()),
+                firstNotBlank(getString(zixiang, "yangpin_bianma"), jilu.getYangpinBianma()),
+                firstNotBlank(getString(zixiang, "guige"), jilu.getGuige()),
+                firstNotBlank(getString(zixiang, "pihao"), jilu.getPihao()),
+                toPlainString(oldXiancunliang),
+                parseDecimal(getString(zixiang, "chuku_shuliang")),
+                firstNotBlank(getString(zixiang, "danwei"), jilu.getDanwei()),
+                firstNotBlank(getString(zixiang, "yongtu"), getString(shenqing, "yongtu")),
+                getObject(shenqing, "shenqing_ren"),
+                getObject(shenqing, "shenqing_riqi"),
+                getObject(shenqing, "fafang_ren"),
+                getObject(shenqing, "shenhe_ren"),
+                getObject(shenqing, "shenhe_riqi"),
+                getObject(shenqing, "fafang_riqi"));
+    }
+
+    private Map<String, Object> findLiuyangXiaohuiShenqing(String flowId, String taskId) {
+        String sql = "select * from " + LIUYANG_XIAOHUI_SHENQING_TABLE
+                + " where f_flow_id = ? and f_flow_task_id = ?"
+                + " and (f_delete_mark is null or f_delete_mark <> 1)"
+                + " limit 1";
+        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, flowId, taskId);
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    private List<String> listLiuyangXiaohuiJiluIds(String shenqingId) {
+        String sql = "select distinct liuyang_jilu_id from " + LIUYANG_XIAOHUI_SHENQING_ZIXIANG_TABLE
+                + " where f_foreign_id = ?"
+                + " and liuyang_jilu_id is not null and liuyang_jilu_id <> ''"
+                + " and (f_delete_mark is null or f_delete_mark <> 1)";
+        return jdbcTemplate.queryForList(sql, shenqingId).stream()
+                .map(row -> getString(row, "liuyang_jilu_id"))
+                .filter(value -> !isBlank(value))
+                .distinct()
+                .collect(Collectors.toList());
+    }
+
+    private void updateLiuyangXiaohuiDanhao(List<String> liuyangJiluIds, String xiaohuiDanhao) {
+        LambdaUpdateWrapper<LimsLiuyangJiluEntity> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.in(LimsLiuyangJiluEntity::getId, liuyangJiluIds)
+                .and(w -> w.isNull(LimsLiuyangJiluEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJiluEntity::getDeleteMark, 1))
+                .set(LimsLiuyangJiluEntity::getXiaohuiDanhao, xiaohuiDanhao);
+        limsLiuyangJiluService.update(wrapper);
+    }
+
+    private void acceptLiuyangXiaohui(List<String> liuyangJiluIds) {
+        LambdaUpdateWrapper<LimsLiuyangJiluEntity> jiluUpdate = new LambdaUpdateWrapper<>();
+        jiluUpdate.in(LimsLiuyangJiluEntity::getId, liuyangJiluIds)
+                .and(w -> w.isNull(LimsLiuyangJiluEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJiluEntity::getDeleteMark, 1))
+                .set(LimsLiuyangJiluEntity::getBizStatus, BIZ_STATUS_DESTROYED);
+        limsLiuyangJiluService.update(jiluUpdate);
+
+        List<String> jihuaIds = listLiuyangJihuaIdsByJiluIds(liuyangJiluIds);
+
+        LambdaUpdateWrapper<LimsLiuyangJihuaEntity> jihuaUpdate = new LambdaUpdateWrapper<>();
+        jihuaUpdate.in(LimsLiuyangJihuaEntity::getLiuyangJiluId, liuyangJiluIds)
+                .and(w -> w.isNull(LimsLiuyangJihuaEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaEntity::getDeleteMark, 1))
+                .set(LimsLiuyangJihuaEntity::getBizStatus, BIZ_STATUS_DISABLED);
+        limsLiuyangJihuaService.update(jihuaUpdate);
+
+        if (!jihuaIds.isEmpty()) {
+            LambdaUpdateWrapper<LimsLiuyangJihuaZixiangEntity> zixiangUpdate = new LambdaUpdateWrapper<>();
+            zixiangUpdate.in(LimsLiuyangJihuaZixiangEntity::getForeignId, jihuaIds)
+                    .and(w -> w.isNull(LimsLiuyangJihuaZixiangEntity::getDeleteMark)
+                            .or()
+                            .ne(LimsLiuyangJihuaZixiangEntity::getDeleteMark, 1))
+                    .set(LimsLiuyangJihuaZixiangEntity::getBizStatus, BIZ_STATUS_DISABLED);
+            limsLiuyangJihuaZixiangService.update(zixiangUpdate);
+        }
+    }
+
+    private List<String> listLiuyangJihuaIdsByJiluIds(List<String> liuyangJiluIds) {
+        LambdaQueryWrapper<LimsLiuyangJihuaEntity> query = new LambdaQueryWrapper<>();
+        query.in(LimsLiuyangJihuaEntity::getLiuyangJiluId, liuyangJiluIds)
+                .and(w -> w.isNull(LimsLiuyangJihuaEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaEntity::getDeleteMark, 1));
+        return limsLiuyangJihuaService.list(query).stream()
+                .map(LimsLiuyangJihuaEntity::getId)
+                .filter(value -> !isBlank(value))
+                .distinct()
+                .collect(Collectors.toList());
+    }
+
+    private String findLiuyangChukuShenqingId(String flowId, String taskId) throws SQLException {
+        String sql = "select f_id from " + LIUYANG_CHUKU_SHENQING_TABLE
+                + " where f_flow_id = ? and f_flow_task_id = ?"
+                + " and (f_delete_mark is null or f_delete_mark <> 1)"
+                + " limit 1";
+        try (Connection connection = dataSource.getConnection();
+             PreparedStatement statement = connection.prepareStatement(sql)) {
+            statement.setString(1, flowId);
+            statement.setString(2, taskId);
+            try (ResultSet resultSet = statement.executeQuery()) {
+                return resultSet.next() ? resultSet.getString("f_id") : null;
+            }
+        }
+    }
+
+    private List<Map<String, String>> listLiuyangChukuShenqingZixiang(String shenqingId) throws SQLException {
+        String sql = "select liuyang_jilu_id, chuku_shuliang, yangpin_mingcheng, pihao from "
+                + LIUYANG_CHUKU_SHENQING_ZIXIANG_TABLE
+                + " where f_foreign_id = ?"
+                + " and (f_delete_mark is null or f_delete_mark <> 1)";
+        List<Map<String, String>> list = new ArrayList<>();
+        try (Connection connection = dataSource.getConnection();
+             PreparedStatement statement = connection.prepareStatement(sql)) {
+            statement.setString(1, shenqingId);
+            try (ResultSet resultSet = statement.executeQuery()) {
+                while (resultSet.next()) {
+                    Map<String, String> row = new LinkedHashMap<>();
+                    row.put("liuyang_jilu_id", resultSet.getString("liuyang_jilu_id"));
+                    row.put("chuku_shuliang", resultSet.getString("chuku_shuliang"));
+                    row.put("yangpin_mingcheng", resultSet.getString("yangpin_mingcheng"));
+                    row.put("pihao", resultSet.getString("pihao"));
+                    list.add(row);
+                }
+            }
+        }
+        return list;
+    }
+
+    private String buildChukuStockErrorMessage(Map<String, ?> zixiang) {
+        return buildChukuStockErrorMessage(zixiang, null);
+    }
+
+    private String buildChukuStockErrorMessage(Map<String, ?> zixiang, LimsLiuyangJiluEntity jilu) {
+        String yangpinMingcheng = firstNotBlank(getString(zixiang, "yangpin_mingcheng"), jilu == null ? null : jilu.getYangpinMingcheng());
+        String pihao = firstNotBlank(getString(zixiang, "pihao"), jilu == null ? null : jilu.getPihao());
+        return trimToEmpty(yangpinMingcheng) + "鎵规" + trimToEmpty(pihao) + "搴撳瓨涓嶈冻锛屼笉婊¤冻鍑哄簱鏉′欢";
+    }
+
+    private BigDecimal parseDecimal(String value) {
+        if (isBlank(value)) {
+            return null;
+        }
+        try {
+            return new BigDecimal(value.trim());
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    private String firstNotBlank(String first, String second) {
+        return isBlank(first) ? second : first;
+    }
+
+    private String trimToEmpty(String value) {
+        return value == null ? "" : value.trim();
+    }
+
+    private String getString(Map<String, ?> row, String key) {
+        if (row == null || key == null) {
+            return null;
+        }
+        Object value = row.get(key);
+        return value == null ? null : String.valueOf(value);
+    }
+
+    private Object getObject(Map<String, ?> row, String key) {
+        if (row == null || key == null) {
+            return null;
+        }
+        return row.get(key);
+    }
+
+    private String toPlainString(BigDecimal value) {
+        return value == null ? null : value.stripTrailingZeros().toPlainString();
+    }
+
+    private void syncBiangengToJihua(String liuyangJiluId, LimsLiuyangBiangengShenqingEntity shenqing) {
+        LambdaUpdateWrapper<LimsLiuyangJihuaEntity> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(LimsLiuyangJihuaEntity::getLiuyangJiluId, liuyangJiluId)
+                .and(w -> w.isNull(LimsLiuyangJihuaEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaEntity::getDeleteMark, 1))
+                .set(LimsLiuyangJihuaEntity::getRukuShuliang, shenqing.getRukuShuliang())
+                .set(LimsLiuyangJihuaEntity::getLiuyangXiancunliang, shenqing.getRukuShuliang())
+                .set(LimsLiuyangJihuaEntity::getDanwei, shenqing.getDanwei())
+                .set(LimsLiuyangJihuaEntity::getLiuyangXiancunliangDanwei, shenqing.getDanwei());
+        limsLiuyangJihuaService.update(wrapper);
+    }
+
+    private LimsLiuyangBiangengShenqingEntity findBiangengShenqing(String id) {
+        LambdaQueryWrapper<LimsLiuyangBiangengShenqingEntity> query = new LambdaQueryWrapper<>();
+        query.eq(LimsLiuyangBiangengShenqingEntity::getId, id)
+                .and(w -> w.isNull(LimsLiuyangBiangengShenqingEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangBiangengShenqingEntity::getDeleteMark, 1));
+        query.last("limit 1");
+        return limsLiuyangBiangengShenqingService.getOne(query, false);
+    }
+
+    private LimsLiuyangJihuaBiangengShenqingEntity findJihuaBiangengShenqing(String flowId, String taskId) {
+        LambdaQueryWrapper<LimsLiuyangJihuaBiangengShenqingEntity> query = new LambdaQueryWrapper<>();
+        query.eq(LimsLiuyangJihuaBiangengShenqingEntity::getFlowId, flowId)
+                .eq(LimsLiuyangJihuaBiangengShenqingEntity::getFlowTaskId, taskId)
+                .and(w -> w.isNull(LimsLiuyangJihuaBiangengShenqingEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaBiangengShenqingEntity::getDeleteMark, 1));
+        query.last("limit 1");
+        return limsLiuyangJihuaBiangengShenqingService.getOne(query, false);
+    }
+
+    private void updateJihuaByBiangeng(LimsLiuyangJihuaBiangengShenqingEntity shenqing) {
+        LimsLiuyangJihuaEntity update = new LimsLiuyangJihuaEntity();
+        update.setId(shenqing.getLiuyangJihuaId());
+        update.setLiuyangRiqi(shenqing.getLiuyangRiqi());
+        update.setLiuyangZhouqiId(shenqing.getLiuyangZhouqiId());
+        limsLiuyangJihuaService.updateById(update);
+    }
+
+    private void softDeleteUnobservedJihuaZixiang(String jihuaId) {
+        LambdaUpdateWrapper<LimsLiuyangJihuaZixiangEntity> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(LimsLiuyangJihuaZixiangEntity::getForeignId, jihuaId)
+                .and(w -> w.isNull(LimsLiuyangJihuaZixiangEntity::getShifouYiguancha)
+                        .or()
+                        .ne(LimsLiuyangJihuaZixiangEntity::getShifouYiguancha, "yes"))
+                .and(w -> w.isNull(LimsLiuyangJihuaZixiangEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaZixiangEntity::getDeleteMark, 1))
+                .set(LimsLiuyangJihuaZixiangEntity::getDeleteMark, 1)
+                .set(LimsLiuyangJihuaZixiangEntity::getDeleteTime, new Date())
+                .set(LimsLiuyangJihuaZixiangEntity::getDeleteUserId, UserProvider.getUser().getUserId());
+        limsLiuyangJihuaZixiangService.update(wrapper);
+    }
+
+    private void syncBiangengZixiangToJihuaZixiang(LimsLiuyangJihuaEntity jihua, LimsLiuyangJihuaBiangengShenqingEntity shenqing) {
+        List<LimsLiuyangJihuaBiangengShenqingZixiangEntity> shenqingZixiangList = listUnobservedJihuaBiangengZixiang(shenqing.getId());
+        if (shenqingZixiangList.isEmpty()) {
+            return;
+        }
+
+        List<LimsLiuyangJihuaZixiangEntity> jihuaZixiangList = shenqingZixiangList.stream()
+                .map(row -> buildJihuaZixiangFromBiangeng(jihua, shenqing, row))
+                .collect(Collectors.toList());
+        limsLiuyangJihuaZixiangService.saveBatch(jihuaZixiangList);
+    }
+
+    private List<LimsLiuyangJihuaBiangengShenqingZixiangEntity> listUnobservedJihuaBiangengZixiang(String shenqingId) {
+        LambdaQueryWrapper<LimsLiuyangJihuaBiangengShenqingZixiangEntity> query = new LambdaQueryWrapper<>();
+        query.eq(LimsLiuyangJihuaBiangengShenqingZixiangEntity::getForeignId, shenqingId)
+                .and(w -> w.isNull(LimsLiuyangJihuaBiangengShenqingZixiangEntity::getShifouYiguancha)
+                        .or()
+                        .ne(LimsLiuyangJihuaBiangengShenqingZixiangEntity::getShifouYiguancha, "yes"))
+                .and(w -> w.isNull(LimsLiuyangJihuaBiangengShenqingZixiangEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaBiangengShenqingZixiangEntity::getDeleteMark, 1))
+                .orderByAsc(LimsLiuyangJihuaBiangengShenqingZixiangEntity::getJihuaRiqi);
+        return limsLiuyangJihuaBiangengShenqingZixiangService.list(query);
+    }
+
+    private LimsLiuyangJihuaZixiangEntity buildJihuaZixiangFromBiangeng(
+            LimsLiuyangJihuaEntity jihua,
+            LimsLiuyangJihuaBiangengShenqingEntity shenqing,
+            LimsLiuyangJihuaBiangengShenqingZixiangEntity shenqingZixiang) {
+        LimsLiuyangJihuaZixiangEntity zixiang = new LimsLiuyangJihuaZixiangEntity();
+        zixiang.setId(RandomUtil.uuId());
+        zixiang.setForeignId(jihua.getId());
+        zixiang.setZhouqi(shenqingZixiang.getZhouqi());
+        zixiang.setZhouqiDanwei(shenqingZixiang.getZhouqiDanwei());
+        zixiang.setJihuaRiqi(shenqingZixiang.getJihuaRiqi());
+        zixiang.setGuanchaRiqi(shenqingZixiang.getGuanchaRiqi());
+        zixiang.setGuanchaRen(shenqingZixiang.getGuanchaRen());
+        zixiang.setGuanchaShuliang(shenqingZixiang.getGuanchaShuliang());
+        zixiang.setGuanchaQingkuang(shenqingZixiang.getGuanchaQingkuang());
+        zixiang.setGuanchaJieguo(shenqingZixiang.getGuanchaJieguo());
+        zixiang.setShifouYiguancha(shenqingZixiang.getShifouYiguancha());
+        zixiang.setBeizhu(shenqingZixiang.getBeizhu());
+        zixiang.setBizType(jihua.getBizType());
+        zixiang.setYangpinMingcheng(jihua.getYangpinMingcheng());
+        zixiang.setYangpinBianma(jihua.getYangpinBianma());
+        zixiang.setGuige(jihua.getGuige());
+        zixiang.setPihao(jihua.getPihao());
+        zixiang.setShengchanRiqi(jihua.getShengchanRiqi());
+        zixiang.setYouxiaoqi(jihua.getYouxiaoqi());
+        zixiang.setLiuyangRiqi(shenqing.getLiuyangRiqi());
+        zixiang.setGuanchaShuliangDanwei(jihua.getDanwei());
+        zixiang.setBizStatus(jihua.getBizStatus());
+        return zixiang;
+    }
+
+    private boolean equalsNullable(String a, String b) {
+        if (a == null && b == null) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        return a.trim().equals(b.trim());
+    }
+
+    private String getFormId(Map<String, Object> formData) {
+        if (formData == null) {
+            return null;
+        }
+        Object id = formData.get("f_id");
+        if (id == null) {
+            id = formData.get("id");
+        }
+        return id == null ? null : String.valueOf(id);
+    }
+
+    private void syncJihuaInfoToZixiang(LimsLiuyangJihuaEntity jihua) {
+        LambdaUpdateWrapper<LimsLiuyangJihuaZixiangEntity> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(LimsLiuyangJihuaZixiangEntity::getForeignId, jihua.getId())
+                .and(w -> w.isNull(LimsLiuyangJihuaZixiangEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaZixiangEntity::getDeleteMark, 1))
+                // 鎻愪氦璁″垝鏃讹紝灏嗕富琛ㄦ牱鍝佷俊鎭啑浣欏埌鍛ㄦ湡瀛愰」锛屼究浜庡悗缁瀵熷垪琛ㄧ嫭绔嬪睍绀恒��
+                .set(LimsLiuyangJihuaZixiangEntity::getBizType, jihua.getBizType())
+                .set(LimsLiuyangJihuaZixiangEntity::getYangpinMingcheng, jihua.getYangpinMingcheng())
+                .set(LimsLiuyangJihuaZixiangEntity::getYangpinBianma, jihua.getYangpinBianma())
+                .set(LimsLiuyangJihuaZixiangEntity::getGuige, jihua.getGuige())
+                .set(LimsLiuyangJihuaZixiangEntity::getPihao, jihua.getPihao())
+                .set(LimsLiuyangJihuaZixiangEntity::getShengchanRiqi, jihua.getShengchanRiqi())
+                .set(LimsLiuyangJihuaZixiangEntity::getYouxiaoqi, jihua.getYouxiaoqi())
+                .set(LimsLiuyangJihuaZixiangEntity::getLiuyangRiqi, jihua.getLiuyangRiqi())
+                .set(LimsLiuyangJihuaZixiangEntity::getGuanchaShuliangDanwei, jihua.getDanwei())
+                .set(LimsLiuyangJihuaZixiangEntity::getBizStatus, jihua.getBizStatus());
+        limsLiuyangJihuaZixiangService.update(wrapper);
+    }
+
+    private LimsLiuyangJiluEntity findLiuyangJilu(String id, String flowId, String taskId) {
+        if (id != null && !id.isEmpty()) {
+            return findById(id);
+        }
+        return findByFlow(flowId, taskId);
+    }
+
+    private LimsLiuyangJiluEntity findById(String id) {
+        LambdaQueryWrapper<LimsLiuyangJiluEntity> query = new LambdaQueryWrapper<>();
+        query.eq(LimsLiuyangJiluEntity::getId, id)
+                .and(w -> w.isNull(LimsLiuyangJiluEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJiluEntity::getDeleteMark, 1));
+        query.last("limit 1");
+        return limsLiuyangJiluService.getOne(query, false);
+    }
+
+    private LimsLiuyangJiluEntity findByFlow(String flowId, String taskId) {
+        LambdaQueryWrapper<LimsLiuyangJiluEntity> query = new LambdaQueryWrapper<>();
+        query.eq(LimsLiuyangJiluEntity::getFlowId, flowId)
+                .and(w -> w.isNull(LimsLiuyangJiluEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJiluEntity::getDeleteMark, 1));
+        if (taskId != null && !taskId.isEmpty()) {
+            query.eq(LimsLiuyangJiluEntity::getFlowTaskId, taskId);
+        }
+        query.last("limit 1");
+        return limsLiuyangJiluService.getOne(query, false);
+    }
+
+    private List<LiuyangWendingxingZhouqiZixiangEntity> listWendingxingZhouqiZixiang(String zhouqiId) {
+        LambdaQueryWrapper<LiuyangWendingxingZhouqiZixiangEntity> query = new LambdaQueryWrapper<>();
+        query.eq(LiuyangWendingxingZhouqiZixiangEntity::getForeignId, zhouqiId)
+                .and(w -> w.isNull(LiuyangWendingxingZhouqiZixiangEntity::getDeleteMark)
+                        .or()
+                        .ne(LiuyangWendingxingZhouqiZixiangEntity::getDeleteMark, 1))
+                .orderByAsc(LiuyangWendingxingZhouqiZixiangEntity::getZhouqi);
+        return liuyangWendingxingZhouqiZixiangService.list(query);
+    }
+
+    private List<LimsLiuyangJihuaZixiangEntity> listObservedJihuaZixiang(String jihuaId) {
+        LambdaQueryWrapper<LimsLiuyangJihuaZixiangEntity> query = new LambdaQueryWrapper<>();
+        query.eq(LimsLiuyangJihuaZixiangEntity::getForeignId, jihuaId)
+                .eq(LimsLiuyangJihuaZixiangEntity::getShifouYiguancha, "yes")
+                .and(w -> w.isNull(LimsLiuyangJihuaZixiangEntity::getDeleteMark)
+                        .or()
+                        .ne(LimsLiuyangJihuaZixiangEntity::getDeleteMark, 1))
+                .orderByAsc(LimsLiuyangJihuaZixiangEntity::getJihuaRiqi);
+        return limsLiuyangJihuaZixiangService.list(query);
+    }
+
+    private LimsLiuyangJihuaCalculateResult buildJihuaZixiang(LiuyangWendingxingZhouqiZixiangEntity zhouqi, Date shouyangRiqi) {
+        if (zhouqi.getZhouqi() == null) {
+            throw new IllegalArgumentException("鍛ㄦ湡涓嶈兘涓虹┖");
+        }
+        if (isBlank(zhouqi.getZhouqiDanwei())) {
+            throw new IllegalArgumentException("鍛ㄦ湡鍗曚綅涓嶈兘涓虹┖");
+        }
+
+        LimsLiuyangJihuaCalculateResult item = new LimsLiuyangJihuaCalculateResult();
+        item.setZhouqi(zhouqi.getZhouqi());
+        item.setZhouqiDanwei(zhouqi.getZhouqiDanwei());
+        item.setJihuaRiqi(calculateJihuaRiqi(shouyangRiqi, zhouqi.getZhouqi(), zhouqi.getZhouqiDanwei()));
+        item.setGuanchaRiqi(null);
+        item.setShifouYiguancha("no");
+        return item;
+    }
+
+    private Map<String, Object> buildBiangengRow(LimsLiuyangJihuaZixiangEntity jihuaZixiang, List<String> responseFields) {
+        Map<String, Object> row = newEmptyBiangengRow(responseFields);
+        putIfPresent(row, "zhouqi", jihuaZixiang.getZhouqi());
+        putIfPresent(row, "zhouqi_danwei", jihuaZixiang.getZhouqiDanwei());
+        putIfPresent(row, "jihua_riqi", jihuaZixiang.getJihuaRiqi());
+        putIfPresent(row, "shifou_yiguancha", jihuaZixiang.getShifouYiguancha());
+        putIfPresent(row, "guancha_riqi", jihuaZixiang.getGuanchaRiqi());
+        putIfPresent(row, "guancha_ren", jihuaZixiang.getGuanchaRen());
+        putIfPresent(row, "guancha_shuliang", jihuaZixiang.getGuanchaShuliang());
+        putIfPresent(row, "guancha_qingkuang", jihuaZixiang.getGuanchaQingkuang());
+        putIfPresent(row, "guancha_jieguo", jihuaZixiang.getGuanchaJieguo());
+        putIfPresent(row, "beizhu", jihuaZixiang.getBeizhu());
+        return row;
+    }
+
+    private Map<String, Object> buildBiangengRow(LiuyangWendingxingZhouqiZixiangEntity zhouqi, Date liuyangRiqi, List<String> responseFields) {
+        if (zhouqi.getZhouqi() == null) {
+            throw new IllegalArgumentException("鍛ㄦ湡涓嶈兘涓虹┖");
+        }
+        if (isBlank(zhouqi.getZhouqiDanwei())) {
+            throw new IllegalArgumentException("鍛ㄦ湡鍗曚綅涓嶈兘涓虹┖");
+        }
+
+        Map<String, Object> row = newEmptyBiangengRow(responseFields);
+        putIfPresent(row, "zhouqi", zhouqi.getZhouqi());
+        putIfPresent(row, "zhouqi_danwei", zhouqi.getZhouqiDanwei());
+        putIfPresent(row, "jihua_riqi", calculateJihuaRiqi(liuyangRiqi, zhouqi.getZhouqi(), zhouqi.getZhouqiDanwei()));
+        putIfPresent(row, "shifou_yiguancha", "no");
+        return row;
+    }
+
+    private Map<String, Object> newEmptyBiangengRow(List<String> responseFields) {
+        Map<String, Object> row = new LinkedHashMap<>();
+        for (String field : responseFields) {
+            row.put(field, null);
+        }
+        return row;
+    }
+
+    private void putIfPresent(Map<String, Object> row, String field, Object value) {
+        if (row.containsKey(field)) {
+            row.put(field, value);
+        }
+    }
+
+    private String buildZhouqiKey(BigDecimal zhouqi, String zhouqiDanwei) {
+        if (zhouqi == null || isBlank(zhouqiDanwei)) {
+            return "";
+        }
+        return zhouqi.stripTrailingZeros().toPlainString() + "|" + zhouqiDanwei.trim();
+    }
+
+    private List<String> getBiangengZixiangBusinessFields() {
+        Set<String> fields = new LinkedHashSet<>();
+        try (Connection connection = dataSource.getConnection()) {
+            DatabaseMetaData metaData = connection.getMetaData();
+            readBusinessColumns(fields, metaData, connection.getCatalog(), connection.getSchema(), BIANGENG_ZIXIANG_TABLE);
+            if (fields.isEmpty()) {
+                readBusinessColumns(fields, metaData, connection.getCatalog(), connection.getSchema(), BIANGENG_ZIXIANG_TABLE.toUpperCase());
+            }
+        } catch (Exception e) {
+            log.warn("璇诲彇{}瀛楁澶辫触锛屼娇鐢ㄩ粯璁ゅ瓧娈�", BIANGENG_ZIXIANG_TABLE, e);
+        }
+        if (fields.isEmpty()) {
+            fields.addAll(DEFAULT_BIANGENG_ZIXIANG_FIELDS);
+        }
+        return new ArrayList<>(fields);
+    }
+
+    private void readBusinessColumns(Set<String> fields, DatabaseMetaData metaData, String catalog, String schema, String tableName) throws SQLException {
+        try (ResultSet columns = metaData.getColumns(catalog, schema, tableName, null)) {
+            while (columns.next()) {
+                String columnName = columns.getString("COLUMN_NAME");
+                if (!isBlank(columnName) && !columnName.toLowerCase().startsWith("f_")) {
+                    fields.add(columnName);
+                }
+            }
+        }
+    }
+
+    private Date calculateJihuaRiqi(Date shouyangRiqi, BigDecimal zhouqi, String zhouqiDanwei) {
+        int amount = toIntegerZhouqi(zhouqi);
+        LocalDateTime baseTime = LocalDateTime.ofInstant(shouyangRiqi.toInstant(), ZoneId.systemDefault());
+        LocalDateTime jihuaTime;
+        switch (zhouqiDanwei.trim()) {
+            case "day":
+            case "澶�":
+                jihuaTime = baseTime.plusDays(amount);
+                break;
+            case "week":
+            case "鍛�":
+                jihuaTime = baseTime.plusWeeks(amount);
+                break;
+            case "month":
+            case "鏈�":
+                jihuaTime = baseTime.plusMonths(amount);
+                break;
+            case "quarter":
+            case "瀛�":
+                jihuaTime = baseTime.plusMonths(amount * 3L);
+                break;
+            case "year":
+            case "骞�":
+                jihuaTime = baseTime.plusYears(amount);
+                break;
+            default:
+                throw new IllegalArgumentException("涓嶆敮鎸佺殑鍛ㄦ湡鍗曚綅锛�" + zhouqiDanwei);
+        }
+        return Date.from(jihuaTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    private int toIntegerZhouqi(BigDecimal zhouqi) {
+        try {
+            return zhouqi.stripTrailingZeros().intValueExact();
+        } catch (ArithmeticException e) {
+            throw new IllegalArgumentException("鍛ㄦ湡蹇呴』涓烘暣鏁帮細" + zhouqi);
+        }
+    }
+
+    private void updateLiuyangStatus(LimsLiuyangJiluEntity liuyangJilu, LiuyangStatus status, String bizSign) {
+        LimsLiuyangJiluEntity update = new LimsLiuyangJiluEntity();
+        update.setId(liuyangJilu.getId());
+        update.setBizStatus(status.getCode());
+        if (bizSign != null && !bizSign.isEmpty()) {
+            update.setBizSign(bizSign);
+        }
+        limsLiuyangJiluService.updateById(update);
+    }
+
+    private void receiveLiuyangJilu(LimsLiuyangJiluEntity liuyangJilu, String bizSign) {
+        LimsLiuyangJiluEntity update = new LimsLiuyangJiluEntity();
+        update.setId(liuyangJilu.getId());
+        update.setBizStatus(LiuyangStatus.RECEIVED.getCode());
+        update.setLiuyangXiancunliang(liuyangJilu.getRukuShuliang());
+        update.setLiuyangXiancunliangDanwei(liuyangJilu.getDanwei());
+        if (bizSign != null && !bizSign.isEmpty()) {
+            update.setBizSign(bizSign);
+        }
+        limsLiuyangJiluService.updateById(update);
+    }
+
+    private void softDeleteLiuyangJilu(LimsLiuyangJiluEntity liuyangJilu, String bizSign) {
+        LimsLiuyangJiluEntity update = new LimsLiuyangJiluEntity();
+        update.setId(liuyangJilu.getId());
+        update.setDeleteMark(1);
+        update.setDeleteTime(new Date());
+        update.setDeleteUserId(UserProvider.getUser().getUserId());
+        if (bizSign != null && !bizSign.isEmpty()) {
+            update.setBizSign(bizSign);
+        }
+        limsLiuyangJiluService.updateById(update);
+    }
+
+    private void updateQingyandanLiuyangJiluId(LimsLiuyangJiluEntity liuyangJilu, String liuyangJiluId) {
+        String qingyandanId = liuyangJilu.getQingyandanId();
+        if (qingyandanId == null || qingyandanId.isEmpty()) {
+            return;
+        }
+
+        LimsQingyandanEntity qingyandan = limsQingyandanService.getById(qingyandanId);
+        if (qingyandan == null) {
+            return;
+        }
+
+        LambdaUpdateWrapper<LimsQingyandanEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(LimsQingyandanEntity::getId, qingyandan.getId())
+                .set(LimsQingyandanEntity::getLiuyangJiluId, liuyangJiluId);
+        limsQingyandanService.update(updateWrapper);
+    }
+
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+}

--
Gitblit v1.8.0