From 9145f8a44c6ae733019e43c775cc30243032e502 Mon Sep 17 00:00:00 2001
From: zwl <1051256694@qq.com>
Date: 星期三, 29 四月 2026 16:16:43 +0800
Subject: [PATCH] 拍照触发修改

---
 src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java | 1144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 1,097 insertions(+), 47 deletions(-)

diff --git a/src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java b/src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java
index f096761..92a24f6 100644
--- a/src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java
+++ b/src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java
@@ -14,11 +14,14 @@
 import com.zy.asrs.mapper.TagMapper;
 import com.zy.asrs.service.*;
 import com.zy.asrs.task.core.ReturnT;
+import com.zy.asrs.task.support.OutboundBatchSeqReleaseGuard;
+import com.zy.asrs.task.support.WorkPublishLockKeys;
 import com.zy.asrs.utils.MatUtils;
 import com.zy.asrs.utils.OrderInAndOutUtil;
 import com.zy.asrs.utils.Utils;
 import com.zy.common.constant.AgvConstant;
 import com.zy.common.constant.ArmConstant;
+import com.zy.common.entity.Parameter;
 import com.zy.common.model.DetlDto;
 import com.zy.common.model.LocDetlDto;
 import com.zy.common.model.LocDto;
@@ -27,12 +30,15 @@
 import com.zy.common.service.CommonService;
 import com.zy.common.utils.HttpHandler;
 import com.zy.common.utils.NodeUtils;
+import com.zy.common.utils.RedisUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -43,10 +49,29 @@
 @Service
 public class OpenServiceImpl implements OpenService {
 
+    private static final Map<Integer, BigDecimal> INBOUND_WEIGHT_FACTOR_BY_SOURCE_STA;
+    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+    private static final int DEFAULT_OUT_ORDER_BATCH_PRIORITY = 100;
+    private static final int DEFAULT_OUT_ORDER_BATCH_PRIORITY_THRESHOLD = 1;
+    // ERP 鍑哄簱鍙eぇ浜庤闃堝�兼椂锛�/outOrder 鍙惤鍑哄簱璁㈠崟锛岀敱瀹氭椂鍣ㄥ悗缁敓鎴愪换鍔°��
+    private static final int PENDING_OUT_ORDER_STATION_THRESHOLD = 600;
+    // 寤惰繜鍑哄簱璁㈠崟浣跨敤鐙珛鍗曟嵁绫诲瀷锛屼究浜庡拰浜哄伐/椤甸潰鍒涘缓鐨勫嚭搴撳崟鍖哄垎鏉ユ簮銆�
+    private static final String OUT_ORDER_PENDING_DOC_TYPE = "鎺ュ彛鍑哄簱鍗�";
+
+    static {
+        Map<Integer, BigDecimal> factorMap = new HashMap<>();
+        factorMap.put(112, new BigDecimal("0.98"));
+        INBOUND_WEIGHT_FACTOR_BY_SOURCE_STA = Collections.unmodifiableMap(factorMap);
+    }
+
     @Autowired
     private OrderService orderService;
     @Autowired
     private OrderDetlService orderDetlService;
+    @Autowired
+    private OrderPakoutService orderPakoutService;
+    @Autowired
+    private OrderDetlPakoutService orderDetlPakoutService;
     @Autowired
     private SnowflakeIdWorker snowflakeIdWorker;
     @Autowired
@@ -89,20 +114,34 @@
     private String stationAddress;
     @Value("${erp.address.URL:}")
     private String erpUrl;
+    @Value("${erp.switch.ErpReportOld}")
+    private boolean erpReportOld;
+    @Value("${erp.address.Inaddress:}")
+    private String erpInAddress;
     @Value("${erp.address.OutErroraddress:}")
     private String erpOutErrorAddress;
     @Autowired
     private WaitPakinService waitPakinService;
     @Autowired
+    private WaitPakinLogService waitPakinLogService;
+    @Autowired
     private WrkMastService wrkMastService;
+    @Autowired
+    private WrkMastLogService wrkMastLogService;
+    @Autowired
+    private WrkDetlLogService wrkDetlLogService;
     @Autowired
     private WcsApiService wcsApiService;
     @Autowired
     private WorkService workService;
     @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
     private BasCrnpService basCrnpService;
     @Autowired
     private ApiLogService apiLogService;
+    @Autowired
+    private OutboundBatchSeqReleaseGuard outboundBatchSeqReleaseGuard;
 
     @Override
     @Transactional
@@ -385,64 +424,131 @@
         if (param.getExecute() == null) {
             throw new CoolException("execute涓嶈兘涓虹┖");
         }
+        // 涓鏃跺厛閬垮紑姝e湪涓嬪彂缁� WCS 鐨勭獥鍙o紝闃叉鏈湴鎶婅鍗曠疆鍋滃悗锛�
+        // WCS 渚т粛鏀跺埌鍚屼竴鎵逛换鍔★紝閫犳垚璁㈠崟鐘舵�佸拰璁惧鎵ц鐘舵�佸垎鍙夈��
+        if (Objects.equals(param.getExecute(), 2)
+                && redisUtil.hasKey(WorkPublishLockKeys.outboundUserNoLock(param.getOrderId()))) {
+            return R.error("姝e湪涓嬪彂浠诲姟缁橶CS锛屾棤娉曚腑姝�");
+        }
 
+        OrderPakout orderPakout = orderPakoutService.selectByNo(param.getOrderId());
         List<WrkMast> activeTasks = findActiveOutboundTasks(param.getOrderId());
         if (Objects.equals(param.getExecute(), 1)) {
-            // ERP纭绔嬪嵆鎵ц锛屼粎澶勭悊寰呬笅鍙戠殑鍑哄簱浠诲姟銆�
-            List<WrkMast> pendingTasks = activeTasks.stream()
-                    .filter(wrkMast -> wrkMast != null && Objects.equals(wrkMast.getWrkSts(), 11L))
-                    .collect(Collectors.toList());
+            // execute=1 鍚屾椂澶勭悊涓ょ被瀵硅薄锛�
+            // 1. 鏈敓鎴愪换鍔$殑鍑哄簱璁㈠崟锛氭仮澶� order.status=1锛岃瀹氭椂鍣ㄥ彲浠ョ户缁壂鎻忋��
+            // 2. 宸茬敓鎴愪絾寰呬笅鍙戠殑浠诲姟锛氳缃� pdcType=Y锛屽厑璁� WorkMastScheduler 涓嬪彂缁� WCS銆�
             Map<String, Object> result = new HashMap<>();
             result.put("orderNo", param.getOrderId());
-            result.put("taskCount", pendingTasks.size());
-            if (pendingTasks.isEmpty()) {
-                result.put("confirmedCount", 0);
-                return R.ok("鏃犳湁鏁堝嚭搴撲换鍔�").add(result);
-            }
-
-            Date now = new Date();
-            int confirmedCount = 0;
-            for (WrkMast wrkMast : pendingTasks) {
-                if (wrkMast == null || "Y".equalsIgnoreCase(wrkMast.getPdcType())) {
-                    continue;
+            result.put("execute", param.getExecute());
+            boolean orderStatusUpdated = false;
+            if (orderPakout != null && !Objects.equals(orderPakout.getStatus(), 1)) {
+                orderPakout.setStatus(1);
+                orderPakout.setUpdateBy(9527L);
+                orderPakout.setUpdateTime(new Date());
+                if (!orderPakoutService.updateById(orderPakout)) {
+                    throw new CoolException("鍚姩鍑哄簱璁㈠崟澶辫触锛�" + param.getOrderId());
                 }
-                wrkMast.setPdcType("Y");
-//            wrkMast.setUpdMk("ERP_CONFIRMED");
-//            wrkMast.setManuType("ERP_CONFIRM_OUT");
-                wrkMast.setModiTime(now);
-                wrkMast.setModiUser(9527L);
-                if (!wrkMastService.updateById(wrkMast)) {
-                    throw new CoolException("纭鎵ц鍑哄簱浠诲姟澶辫触: " + wrkMast.getWrkNo());
-                }
-                confirmedCount++;
+                orderStatusUpdated = true;
             }
-
-            result.put("confirmedCount", confirmedCount);
-            return R.ok(confirmedCount == 0 ? "浠诲姟宸茬‘璁ゆ墽琛�" : "ERP纭鎵ц鍑哄簱鎴愬姛").add(result);
+            int confirmedCount = confirmPendingOutboundTasks(activeTasks);
+            R generateResult = null;
+            int generatedConfirmedCount = 0;
+            if (orderPakout != null) {
+                // 鍚姩鍚庣珛鍗冲皾璇曠敓鎴愬綋鍓嶅彲鏀捐鐨勮繘浠撶紪鍙锋壒娆★紝閬垮厤蹇呴』绛夊緟涓嬩竴杞畾鏃舵壂鎻忋��
+                generateResult = generatePendingPakoutOrderTasks(param.getOrderId());
+                // 鍒氱敓鎴愮殑浠诲姟鍒濆鐘舵�佷粛鏄� 11锛岃繖閲屽啀娆$‘璁わ紝淇濇寔鈥滃惎鍔ㄦ帴鍙h皟鐢ㄥ悗鍗冲彲涓嬪彂鈥濈殑璇箟銆�
+                generatedConfirmedCount = confirmPendingOutboundTasks(findActiveOutboundTasks(param.getOrderId()));
+            }
+            result.put("orderStatusUpdated", orderStatusUpdated);
+            result.put("confirmedCount", confirmedCount + generatedConfirmedCount);
+            result.put("generatedConfirmedCount", generatedConfirmedCount);
+            result.put("generateResult", generateResult);
+            return R.ok("鍑哄簱璁㈠崟鍚姩鎴愬姛").add(result);
         }
         if (Objects.equals(param.getExecute(), 2)) {
-            // ERP璇锋眰鍙栨秷浠诲姟锛氱洿鎺ユ敹闆嗕换鍔″彿锛屾寜 taskList 鏍煎紡鍙戦�佺粰 WCS銆�
+            // execute=2 鍏堝叧闂鍗曞紑鍏筹紝闃绘瀹氭椂鍣ㄧ户缁负鏈敓鎴愪换鍔$殑鏄庣粏寤� WrkMast銆�
+            // 宸茬粡鐢熸垚鐨勪换鍔℃寜鐘舵�佸垎娴侊細11 灏氭湭涓嬪彂锛岃蛋鏈湴鍙栨秷锛�12/13 宸蹭笅鍙戞垨鎵ц涓紝闇�瑕侀�氱煡 WCS 鍙栨秷銆�
             Map<String, Object> result = new HashMap<>();
             result.put("orderNo", param.getOrderId());
             result.put("execute", param.getExecute());
             result.put("taskCount", activeTasks.size());
+            boolean orderStatusUpdated = false;
+            if (orderPakout != null && !Objects.equals(orderPakout.getStatus(), 0)) {
+                orderPakout.setStatus(0);
+                orderPakout.setUpdateBy(9527L);
+                orderPakout.setUpdateTime(new Date());
+                if (!orderPakoutService.updateById(orderPakout)) {
+                    throw new CoolException("涓鍑哄簱璁㈠崟澶辫触锛�" + param.getOrderId());
+                }
+                orderStatusUpdated = true;
+            }
+            result.put("orderStatusUpdated", orderStatusUpdated);
             if (activeTasks.isEmpty()) {
-                return R.ok("鏃犳湁鏁堝嚭搴撲换鍔�").add(result);
+                result.put("cancelledLocalTaskCount", 0);
+                result.put("pausedWcsTaskCount", 0);
+                return R.ok("鍑哄簱璁㈠崟宸蹭腑姝�").add(result);
             }
             List<HashMap<String,Object>> taskList = new ArrayList<>();
+            List<WrkMast> wcsCancelTasks = new ArrayList<>();
+            int cancelledLocalTaskCount = 0;
             for (WrkMast wrkMast : activeTasks) {
                 HashMap<String,Object> hashMap = new HashMap<>();
                 hashMap.put("taskNo", wrkMast.getWrkNo());
-                if (!Cools.isEmpty(wrkMast) && wrkMast.getWrkSts() ==11L) {
+                if (!Cools.isEmpty(wrkMast) && Objects.equals(wrkMast.getWrkSts(), 11L)) {
+                    // wrk_sts=11 杩樻病鍙戝埌 WCS锛屾湰鍦板彇娑堝嵆鍙紝涓嶉渶瑕佷骇鐢� WCS 鏆傚仠鎸囦护銆�
                     workService.cancelWrkMast(wrkMast.getWrkNo()+"", 9955L);
+                    cancelledLocalTaskCount++;
                     continue;
                 }
                 taskList.add(hashMap);
+                wcsCancelTasks.add(wrkMast);
             }
-            wcsApiService.pauseOutTasks(taskList);
-            return R.ok("鍙栨秷浠诲姟宸插彂閫佽嚦WCS").add(result);
+            int cancelledWcsTaskCount = 0;
+            if (!taskList.isEmpty()) {
+                // wrk_sts=12/13 绛夊凡杩涘叆 WCS 渚х殑浠诲姟蹇呴』鏍¢獙 WCS 杩斿洖锛屽け璐ュ垯浜嬪姟鍥炴粴锛岄伩鍏嶆湰鍦拌鎶ュ凡鍙栨秷銆�
+                R wcsR = wcsApiService.pauseOutTasks(taskList);
+                requireWcsPauseOk(wcsR);
+                // WCS 宸茬‘璁ゅ彇娑堝悗锛屾湰鍦颁篃瑕佸彇娑堜换鍔″苟鍥炴粴璁㈠崟鏄庣粏 work_qty銆�
+                // 鍥炴粴閫昏緫鍦� WorkService.cancelWrkMast 涓粺涓�澶勭悊锛岀‘淇� WCS 鍥炶皟 task_cancel 璧板悓涓�濂楀彛寰勩��
+                for (WrkMast wrkMast : wcsCancelTasks) {
+                    workService.cancelWrkMast(wrkMast.getWrkNo()+"", 9955L);
+                    cancelledWcsTaskCount++;
+                }
+            }
+            result.put("cancelledLocalTaskCount", cancelledLocalTaskCount);
+            result.put("pausedWcsTaskCount", taskList.size());
+            result.put("cancelledWcsTaskCount", cancelledWcsTaskCount);
+            return R.ok("鍑哄簱璁㈠崟宸蹭腑姝�").add(result);
         }
         throw new CoolException("reason浠呮敮鎸�1鎴�2");
+    }
+
+    /**
+     * 灏嗗凡鐢熸垚浣嗚繕鏈厑璁镐笅鍙戠殑鍑哄簱浠诲姟缃负鍙笅鍙戙��
+     *
+     * pdcType 鏄幇鏈� WorkMastScheduler 鐨勪笅鍙戝紑鍏筹細
+     * - wrk_sts=11 涓� pdcType=Y 鏃讹紝璋冨害鍣ㄥ彲浠ョ户缁笅鍙戠粰 WCS銆�
+     * - 宸茬粡鏄� Y 鐨勪换鍔′繚鎸佸箓绛夛紝涓嶉噸澶嶆洿鏂般��
+     */
+    private int confirmPendingOutboundTasks(List<WrkMast> activeTasks) {
+        if (Cools.isEmpty(activeTasks)) {
+            return 0;
+        }
+        Date now = new Date();
+        int confirmedCount = 0;
+        for (WrkMast wrkMast : activeTasks) {
+            if (wrkMast == null || !Objects.equals(wrkMast.getWrkSts(), 11L) || "Y".equalsIgnoreCase(wrkMast.getPdcType())) {
+                continue;
+            }
+            wrkMast.setPdcType("Y");
+            wrkMast.setModiTime(now);
+            wrkMast.setModiUser(9527L);
+            if (!wrkMastService.updateById(wrkMast)) {
+                throw new CoolException("纭鎵ц鍑哄簱浠诲姟澶辫触: " + wrkMast.getWrkNo());
+            }
+            confirmedCount++;
+        }
+        return confirmedCount;
     }
 
     /** WCS 杩斿洖闈炴垚鍔熺爜鏃舵姏閿� */
@@ -659,6 +765,117 @@
     @Transactional
     public List<StockVo> queryStock() {
         return locDetlService.queryStockTotal();
+    }
+
+    @Override
+    public R reportPakinHistoryToErp(List<String> barcodes) {
+        List<String> normalizedBarcodes = normalizeBarcodes(barcodes);
+        if (normalizedBarcodes.isEmpty()) {
+            return R.error("鎵樼洏鐮侀泦鍚堜笉鑳戒负绌�");
+        }
+        if (!isErpReportEnabled()) {
+            return R.error("ERP reporting is disabled");
+        }
+        if (Cools.isEmpty(erpInAddress)) {
+            return R.error("ERP鍏ュ簱涓婃姤鍦板潃鏈厤缃�");
+        }
+
+        Map<String, WrkMastLog> latestInboundLogMap = loadLatestInboundHistoryLogMap(normalizedBarcodes);
+        Map<Integer, List<WrkDetlLog>> wrkDetlLogMap = loadWrkDetlLogMap(latestInboundLogMap.values());
+        Map<String, WaitPakinLog> waitPakinLogMap = loadWaitPakinLogMap(normalizedBarcodes);
+        String requestUrl = buildErpInboundRequestUrl();
+
+        List<Map<String, Object>> rows = new ArrayList<>();
+        int successCount = 0;
+        int failCount = 0;
+
+        for (String barcode : normalizedBarcodes) {
+            Map<String, Object> row = new LinkedHashMap<>();
+            row.put("barcode", barcode);
+
+            WrkMastLog wrkMastLog = latestInboundLogMap.get(barcode);
+            if (wrkMastLog == null) {
+                row.put("success", false);
+                row.put("message", "鏈壘鍒版渶鏂板叆搴撳巻鍙茶褰�");
+                rows.add(row);
+                failCount++;
+                continue;
+            }
+
+            WaitPakinLog waitPakinLog = waitPakinLogMap.get(barcode);
+            List<WrkDetlLog> wrkDetlLogs = wrkDetlLogMap.getOrDefault(wrkMastLog.getWrkNo(), Collections.emptyList());
+            ErpPakinReportParam param = buildInboundErpParam(barcode, wrkMastLog, wrkDetlLogs, waitPakinLog);
+            if (Cools.isEmpty(param.getPalletId())) {
+                row.put("success", false);
+                row.put("message", "鎵樼洏鐮佺己灏戜笂鎶ュ瓧娈礫palletId]");
+                rows.add(row);
+                failCount++;
+                continue;
+            }
+            if (Cools.isEmpty(param.getLocId())) {
+                row.put("success", false);
+                row.put("message", "鎵樼洏鐮佺己灏戜笂鎶ュ瓧娈礫locId]");
+                rows.add(row);
+                failCount++;
+                continue;
+            }
+
+            String request = JSON.toJSONString(param);
+            String response = "";
+            boolean success = false;
+            String errorMsg = null;
+            try {
+                response = new HttpHandler.Builder()
+                        .setUri(erpUrl)
+                        .setPath(erpInAddress)
+                        .setJson(request)
+                        .build()
+                        .doPost();
+                success = isErpCallSuccess(response);
+                if (!success) {
+                    errorMsg = extractErpCallError(response);
+                }
+            } catch (Exception e) {
+                errorMsg = e.getMessage();
+            } finally {
+                try {
+                    apiLogService.save(
+                            "Inbound ERP Report",
+                            requestUrl,
+                            null,
+                            "127.0.0.1",
+                            request,
+                            response,
+                            success,
+                            "barcode=" + barcode + ",workNo=" + wrkMastLog.getWrkNo()
+                    );
+                } catch (Exception logEx) {
+                    log.error("save inbound erp api log failed", logEx);
+                }
+            }
+
+            row.put("workNo", wrkMastLog.getWrkNo());
+            Integer sourceStaNo = resolveInboundSourceStaNo(wrkMastLog);
+            row.put("sourceStaNo", sourceStaNo);
+            row.put("weightFactor", resolveInboundWeightFactor(sourceStaNo));
+            row.put("erpWeight", param.getWeight());
+            row.put("success", success);
+            row.put("message", success ? "OK" : errorMsg);
+            rows.add(row);
+
+            if (success) {
+                successCount++;
+            } else {
+                failCount++;
+            }
+        }
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("total", normalizedBarcodes.size());
+        result.put("successCount", successCount);
+        result.put("failCount", failCount);
+        result.put("rows", rows);
+        return R.ok().add(result);
     }
 
     @Override
@@ -1337,6 +1554,12 @@
 
         waitPakin.setIoStatus("N");     // 鍏ュ嚭鐘舵��
         waitPakin.setAnfme(param.getAnfme());  // 鏁伴噺
+        waitPakin.setFreqType(param.getFreqType());
+        waitPakin.setContainerNo(param.getContainerNo());
+        waitPakin.setTeu(param.getTeu());
+        waitPakin.setPlateNo(param.getPlateNo());
+        waitPakin.setTrainNo(param.getTrainNo());
+        waitPakin.setCubeNumber(param.getCubeNumber());
         waitPakin.setStatus("Y");    // 鐘舵��
         waitPakin.setAppeUser(9995L);
         waitPakin.setAppeTime(now);
@@ -1365,7 +1588,11 @@
      * 7.11 鍑哄簱閫氱煡鍗曪紙浼犻�掓湁搴忔棤搴忚鍒欙級鍗曟潯寤哄崟銆�
      */
     @Override
-    public R outOrder(OutTaskParam param,int count) {
+    public R outOrder(OutTaskParam param,int count,int i) {
+        return outOrder(param, count, 0 ,i);
+    }
+
+    private R outOrder(OutTaskParam param, int count, int teu , int i) {
         LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_sts", "F").eq("barcode", param.getPalletId()));
         if (locMast == null) {
             throw new CoolException("娌℃湁鎵惧埌鎵樼洏鐮�=" + param.getPalletId() + "瀵瑰簲鐨勫簱浣�");
@@ -1400,7 +1627,7 @@
         wrkMast.setIoTime(now);
         wrkMast.setWrkSts(11L); // 宸ヤ綔鐘舵�侊細11.鐢熸垚鍑哄簱ID
         wrkMast.setIoType(ioType); // 鍏ュ嚭搴撶姸鎬�
-        wrkMast.setIoPri(13D); // 浼樺厛绾э細13
+        wrkMast.setIoPri(Double.valueOf(i)); // 浼樺厛绾�
         wrkMast.setCrnNo(locMast.getCrnNo());
         wrkMast.setSourceStaNo(staDesc.getCrnStn()); // 婧愮珯
         wrkMast.setStaNo(staDesc.getStnNo()); // 鐩爣绔�
@@ -1411,7 +1638,13 @@
         wrkMast.setExitMk("N"); // 閫�鍑�
         wrkMast.setEmptyMk("N"); // 绌烘澘
         wrkMast.setLinkMis("N");
-        wrkMast.setPdcType("N");
+        wrkMast.setContainerNo(param.getContainerNo());
+        wrkMast.setTeu(teu);
+        wrkMast.setPdcType(locMast.getCrnNo()>=19?"Y":"N");//鑷姩浠诲姟涓嬪彂鏍囪
+        wrkMast.setPlateNo(param.getPlateNo());
+        wrkMast.setTrainNo(param.getTrainNo());
+        wrkMast.setFreqType(param.getFreqType());
+        wrkMast.setCubeNumber(param.getCubeNumber());
         // 7.11锛歰rderId 瀛� userNo锛宐atchSeq 瀛樻壒娆℃爣璇嗭紝seq 瀛樻壒娆″唴椤哄簭銆�
         wrkMast.setUserNo(param.getOrderId());//璁㈠崟鍙�
         wrkMast.setBatchSeq(param.getBatchSeq());//璁㈠崟鍐呮壒娆℃爣璇�
@@ -1441,10 +1674,17 @@
             wrkDetl.setAppeUser(9995L);
             wrkDetl.setModiTime(now);
             wrkDetl.setModiUser(9995L);
+            wrkDetl.setTeu(teu);
+            wrkDetl.setContainerNo(param.getContainerNo());
+            wrkDetl.setPlateNo(param.getPlateNo());
+            wrkDetl.setTrainNo(param.getTrainNo());
+            wrkDetl.setFreqType(param.getFreqType());
+            wrkDetl.setCubeNumber(param.getCubeNumber());
             // 7.11锛歟ntryWmsCode銆乷utDoorNo 澶嶇敤鏄庣粏澶囩敤瀛楁銆�
             wrkDetl.setStandby1(param.getEntryWmsCode());
             wrkDetl.setStandby2(param.getOutDoorNo());
-            wrkDetl.setSupp(param.getSeq()+"/"+count);
+            wrkDetl.setSupp(count+"");
+            wrkDetl.setTeu(param.getTeu());
 
             if (!wrkDetlService.insert(wrkDetl)) {
                 throw new CoolException("淇濆瓨宸ヤ綔妗f槑缁嗗け璐�");
@@ -1471,20 +1711,518 @@
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public R outOrderBatch(List<OutTaskParam> params) {
-        int n = params.size();
-        Map<String, Integer> batchLineCounts = new HashMap<>();
-        for (OutTaskParam outTaskParam : params) {
-            batchLineCounts.merge(buildOutOrderBatchKey(outTaskParam), 1, Integer::sum);
-        }
-        for (OutTaskParam outTaskParam : params) {
-            int count = batchLineCounts.getOrDefault(buildOutOrderBatchKey(outTaskParam), n);
-            R r = outOrder(outTaskParam, count);
-            if (!Objects.equals(r.get("code"), 200)) {
-                throw new CoolException("鍑哄簱寤哄崟澶辫触");
+    public R outOrderBatch(Map<String, List<OutTaskParam>> linesByBatchSeq,int count) {
+
+        for (Map.Entry<String, List<OutTaskParam>> entry : linesByBatchSeq.entrySet()) {
+            int i = DEFAULT_OUT_ORDER_BATCH_PRIORITY;
+            int j = 0;
+            int priorityThreshold = getOutOrderBatchPriorityThreshold();
+            for (OutTaskParam outTaskParam : entry.getValue()) {
+                int teu = Cools.isEmpty(outTaskParam.getTeu())?0:outTaskParam.getTeu();
+                R r = outOrder(outTaskParam, count, teu ,i);
+                if (!Objects.equals(r.get("code"), 200)) {
+                    throw new CoolException("鍑哄簱寤哄崟澶辫触");
+                }
+                j++;
+                if (j >= priorityThreshold) {
+                    i--;
+                    j = 0;
+                }
             }
+
         }
         return R.ok();
+    }
+
+    private int getOutOrderBatchPriorityThreshold() {
+        Parameter parameter = Parameter.get();
+        if (parameter == null || Cools.isEmpty(parameter.getOutOrderBatchPriorityThreshold())) {
+            return DEFAULT_OUT_ORDER_BATCH_PRIORITY_THRESHOLD;
+        }
+        try {
+            int threshold = Integer.parseInt(parameter.getOutOrderBatchPriorityThreshold().trim());
+            return threshold <= 0 ? DEFAULT_OUT_ORDER_BATCH_PRIORITY_THRESHOLD : threshold;
+        } catch (NumberFormatException ignored) {
+            return DEFAULT_OUT_ORDER_BATCH_PRIORITY_THRESHOLD;
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R outOrderCreatePakoutOrder(List<OutTaskParam> params) {
+        if (Cools.isEmpty(params)) {
+            return R.ok();
+        }
+
+        // 涓�涓� /outOrder 璇锋眰鍙兘鍖呭惈澶氫釜 orderId銆傛寜璁㈠崟鍙峰垎缁勫悗鍒嗗埆鍒涘缓璁㈠崟澶达紝
+        // 淇濊瘉 orderId 濮嬬粓瀵瑰簲 man_order_pakout.order_no锛屾槑缁嗘寕鍦ㄥ搴旇鍗曚笅銆�
+        Map<String, List<OutTaskParam>> paramsByOrderNo = new LinkedHashMap<>();
+        for (OutTaskParam param : params) {
+            validatePendingOutOrderParam(param);
+            paramsByOrderNo.computeIfAbsent(param.getOrderId(), key -> new ArrayList<>()).add(param);
+        }
+
+        Date now = new Date();
+        int orderCount = 0;
+        int detailCount = 0;
+        int removedUndispatchedDetailCount = 0;
+        List<String> orderNos = new ArrayList<>();
+        for (Map.Entry<String, List<OutTaskParam>> entry : paramsByOrderNo.entrySet()) {
+            String orderNo = entry.getKey();
+            // 寤惰繜寤哄崟鍏佽鍚屼竴涓� orderNo 鍐嶆涓嬪彂锛�
+            // 宸蹭笅鍙�/宸插畬鎴愭槑缁嗕繚鐣欒拷婧紱鏈笅鍙戞槑缁嗗垹闄ゅ悗锛屼娇鐢ㄦ湰娆℃帴鍙e弬鏁伴噸鏂版彃鍏ャ��
+            assertPendingPakoutOrderCanReplace(orderNo);
+
+            OrderPakout order = orderPakoutService.selectByNo(orderNo);
+            if (order == null) {
+                order = createPendingPakoutOrderHeader(orderNo, now);
+            } else {
+                assertNoNonReplaceablePendingDetailConflict(order, entry.getValue());
+                removedUndispatchedDetailCount += removeUndispatchedPendingDetails(order.getId());
+                refreshPendingPakoutOrderForResubmit(order, now);
+            }
+
+            for (OutTaskParam param : entry.getValue()) {
+                // 鏄庣粏瀹屾暣淇濆瓨鎺ュ彛瀛楁锛屽悗缁畾鏃跺櫒鍙互鏃犳崯杩樺師 OutTaskParam 鍐嶅鐢� outOrderBatch銆�
+                OrderDetlPakout detail = buildPendingPakoutOrderDetl(order, param, now);
+                if (!orderDetlPakoutService.insert(detail)) {
+                    throw new CoolException("鐢熸垚鍑哄簱璁㈠崟鏄庣粏澶辫触锛�" + orderNo);
+                }
+                detailCount++;
+            }
+            orderCount++;
+            orderNos.add(orderNo);
+        }
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("orderCount", orderCount);
+        result.put("detailCount", detailCount);
+        result.put("removedUndispatchedDetailCount", removedUndispatchedDetailCount);
+        result.put("orderNos", orderNos);
+        return R.ok("鍑哄簱璁㈠崟鐢熸垚鎴愬姛").add(result);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R generatePendingPakoutOrderTasks() {
+        // 瀹氭椂鍏ュ彛鍙壂鎻忓惎鐢ㄤ腑鐨勫嚭搴撹鍗曪細
+        // status=1 琛ㄧず鏈 pakoutOrderPause 涓锛�
+        // settle in (1,2) 琛ㄧず鏈畬鎴愬叏閮ㄤ换鍔$敓鎴愶紱
+        // pakin_pakout_status=2 闄愬畾鍑哄簱鍗曪紝閬垮厤璇壂鍏ュ簱/鍏朵粬鏂瑰悜鍗曟嵁銆�
+        List<OrderPakout> orders = orderPakoutService.selectList(new EntityWrapper<OrderPakout>()
+                .eq("status", 1)
+                .in("settle", Arrays.asList(1L, 2L))
+                .eq("pakin_pakout_status", 2)
+                .orderBy("create_time", true));
+        Map<String, Object> result = new LinkedHashMap<>();
+        int scannedOrderCount = 0;
+        int generatedOrderCount = 0;
+        int generatedTaskCount = 0;
+        List<Object> details = new ArrayList<>();
+        if (!Cools.isEmpty(orders)) {
+            for (OrderPakout order : orders) {
+                if (order == null || Cools.isEmpty(order.getOrderNo())) {
+                    continue;
+                }
+                scannedOrderCount++;
+                // 姣忎釜璁㈠崟鍗曠嫭璧扮敓鎴愰�昏緫銆傚崟璁㈠崟鏂规硶鍐呴儴鍙細鐢熸垚褰撳墠鍏佽鐨勪竴涓� entryWmsCode 鎵规锛�
+                // 鍥犳瀹氭椂鍣ㄩ噸澶嶆墽琛屼篃涓嶄細涓�娆℃�ф妸鍚庣画鎵�鏈夎繘浠撶紪鍙峰叏閮ㄩ噴鏀俱��
+                R r = generatePendingPakoutOrderTasks(order.getOrderNo());
+                details.add(r);
+                int taskCount = extractGeneratedTaskCount(r);
+                if (taskCount > 0) {
+                    generatedOrderCount++;
+                    generatedTaskCount += taskCount;
+                }
+            }
+        }
+        result.put("scannedOrderCount", scannedOrderCount);
+        result.put("generatedOrderCount", generatedOrderCount);
+        result.put("generatedTaskCount", generatedTaskCount);
+        result.put("details", details);
+        return R.ok().add(result);
+    }
+
+    private int extractGeneratedTaskCount(R r) {
+        if (r == null) {
+            return 0;
+        }
+        Object generatedObj = r.get("generatedTaskCount");
+        if (generatedObj instanceof Number) {
+            return ((Number) generatedObj).intValue();
+        }
+        Object dataObj = r.get("data");
+        if (dataObj instanceof Map) {
+            Object dataGeneratedObj = ((Map) dataObj).get("generatedTaskCount");
+            if (dataGeneratedObj instanceof Number) {
+                return ((Number) dataGeneratedObj).intValue();
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R generatePendingPakoutOrderTasks(String orderNo) {
+        if (Cools.isEmpty(orderNo)) {
+            throw new CoolException("orderNo涓嶈兘涓虹┖");
+        }
+        OrderPakout order = orderPakoutService.selectByNo(orderNo);
+        if (order == null) {
+            return R.ok("鍑哄簱璁㈠崟涓嶅瓨鍦�").add(buildGeneratePendingResult(orderNo, null, 0, 0, "order not found"));
+        }
+        if (!Objects.equals(order.getStatus(), 1)) {
+            return R.ok("鍑哄簱璁㈠崟宸蹭腑姝�").add(buildGeneratePendingResult(orderNo, null, 0, 0, "order status disabled"));
+        }
+        if (!Objects.equals(order.getSettle(), 1L) && !Objects.equals(order.getSettle(), 2L)) {
+            return R.ok("鍑哄簱璁㈠崟宸插鐞�").add(buildGeneratePendingResult(orderNo, null, 0, 0, "order settle not pending"));
+        }
+
+        // 鎸夊師鏄庣粏 id 椤哄簭璇诲彇锛孡inkedHashMap 浼氫繚鎸侀娆″嚭鐜扮殑 entryWmsCode 椤哄簭銆�
+        // 杩欐牱鈥滆繘浠撶紪鍙� A 鍋氬畬鍚庡啀鐢熸垚 B鈥濈殑椤哄簭鍜� ERP 鍘熷鏄庣粏椤哄簭涓�鑷淬��
+        List<OrderDetlPakout> details = orderDetlPakoutService.selectList(new EntityWrapper<OrderDetlPakout>()
+                .eq("order_id", order.getId())
+                .eq("status", 1)
+                .orderBy("id", true));
+        LinkedHashMap<String, List<OrderDetlPakout>> detailsByEntryWmsCode = new LinkedHashMap<>();
+        for (OrderDetlPakout detail : details) {
+            if (!isPendingPakoutDetail(detail)) {
+                continue;
+            }
+            detailsByEntryWmsCode
+                    .computeIfAbsent(detail.getEntryWmsCode(), key -> new ArrayList<>())
+                    .add(detail);
+        }
+        if (detailsByEntryWmsCode.isEmpty()) {
+            return R.ok("鏃犲緟鐢熸垚鍑哄簱浠诲姟").add(buildGeneratePendingResult(orderNo, null, 0, 0, "no pending detail"));
+        }
+
+        // 涓�娆″彧鍙栧綋鍓嶆渶闈犲墠鐨勮繘浠撶紪鍙锋壒娆°�傚悗缁繘浠撶紪鍙锋槸鍚﹀厑璁哥敓鎴愶紝
+        // 鐢� OutboundBatchSeqReleaseGuard 鏍规嵁宸叉湁 WrkMast 鐨� batchSeq銆亀rk_sts=25 鏁伴噺闃堝�肩瓑鍒ゆ柇銆�
+        Map.Entry<String, List<OrderDetlPakout>> candidate = detailsByEntryWmsCode.entrySet().iterator().next();
+        String entryWmsCode = candidate.getKey();
+        String blockMsg = outboundBatchSeqReleaseGuard.validateReady(orderNo, entryWmsCode);
+        if (!Cools.isEmpty(blockMsg)) {
+            return R.ok("鍑哄簱杩涗粨缂栧彿鏆備笉婊¤冻鐢熸垚鏉′欢").add(buildGeneratePendingResult(orderNo, entryWmsCode, 0, candidate.getValue().size(), blockMsg));
+        }
+
+        List<OutTaskParam> outTaskParams = new ArrayList<>();
+        for (OrderDetlPakout detail : candidate.getValue()) {
+            outTaskParams.add(buildOutTaskParam(orderNo, entryWmsCode, detail));
+        }
+        Map<String, List<OutTaskParam>> linesByBatchSeq = new LinkedHashMap<>();
+        // outOrderBatch 鐨勫垎缁� key 灏辨槸浠诲姟 batchSeq銆傝繖閲屽己鍒朵娇鐢� entryWmsCode锛�
+        // 纭繚 WrkMast.userNo=orderId 涓� WrkMast.batchSeq=entryWmsCode锛岀幇鏈夋壒娆″畧鍗墠鑳界敓鏁堛��
+        linesByBatchSeq.put(entryWmsCode, outTaskParams);
+        R r = outOrderBatch(linesByBatchSeq, outTaskParams.size());
+        if (!Objects.equals(r.get("code"), 200)) {
+            throw new CoolException("鍑哄簱璁㈠崟鐢熸垚浠诲姟澶辫触锛�" + orderNo);
+        }
+
+        for (OrderDetlPakout detail : candidate.getValue()) {
+            double remaining = getPendingDetailQty(detail);
+            // work_qty 琛ㄧず鈥滃凡鐢熸垚浠诲姟鏁伴噺鈥濓紝涓嶆槸瀹屾垚鏁伴噺銆�
+            // 浠诲姟瀹屾垚鍚庣敱 WorkMastHandler 閫掑 qty锛屽洜姝ら噸澶嶅畾鏃舵壂鎻忎笉浼氫负鍚屼竴鎵樼洏閲嶅寤轰换鍔°��
+            detail.setWorkQty(safeDouble(detail.getWorkQty()) + remaining);
+            detail.setUpdateBy(9527L);
+            detail.setUpdateTime(new Date());
+            if (!orderDetlPakoutService.updateById(detail)) {
+                throw new CoolException("鏇存柊鍑哄簱璁㈠崟鏄庣粏浣滀笟鏁伴噺澶辫触锛�" + orderNo);
+            }
+        }
+        if (Objects.equals(order.getSettle(), 1L)) {
+            // 璁㈠崟涓�鏃︾敓鎴愯繃浠诲姟鍗崇疆涓� 2銆傛槸鍚﹁繕鏈夋槑缁嗘湭鐢熸垚锛屼粛浠ユ槑缁� anfme - work_qty 鍒ゆ柇銆�
+            order.setSettle(2L);
+            order.setUpdateBy(9527L);
+            order.setUpdateTime(new Date());
+            if (!orderPakoutService.updateById(order)) {
+                throw new CoolException("鏇存柊鍑哄簱璁㈠崟鐘舵�佸け璐ワ細" + orderNo);
+            }
+        }
+
+        return R.ok("鍑哄簱璁㈠崟鐢熸垚浠诲姟鎴愬姛").add(buildGeneratePendingResult(orderNo, entryWmsCode, outTaskParams.size(), candidate.getValue().size(), null));
+    }
+
+    private void validatePendingOutOrderParam(OutTaskParam param) {
+        if (param == null || Cools.isEmpty(param.getOrderId())) {
+            throw new CoolException("鍑哄簱鍗曞彿涓嶈兘涓虹┖");
+        }
+        if (Cools.isEmpty(param.getPalletId())) {
+            throw new CoolException("鎵樼洏鍙蜂笉鑳戒负绌�");
+        }
+        if (Cools.isEmpty(param.getEntryWmsCode())) {
+            throw new CoolException("鎵樼洏銆�" + param.getPalletId() + "銆嶈繘浠撶紪鍙蜂笉鑳戒负绌�");
+        }
+        if (!isPendingOutOrderStation(param.getStationId())) {
+            throw new CoolException("鎵樼洏銆�" + param.getPalletId() + "銆嶅嚭搴撳彛涓嶅睘浜庡欢杩熺敓鎴愪换鍔¤寖鍥�");
+        }
+    }
+
+    private OrderPakout createPendingPakoutOrderHeader(String orderNo, Date now) {
+        DocType docType = docTypeService.selectOrAdd(OUT_ORDER_PENDING_DOC_TYPE, Boolean.FALSE);
+        OrderPakout order = new OrderPakout();
+        order.setUuid(String.valueOf(snowflakeIdWorker.nextId()));
+        order.setOrderNo(orderNo);
+        order.setOrderTime(DateUtils.convert(now));
+        order.setDocType(docType.getDocId());
+        // settle=1 琛ㄧず寰呯敓鎴愪换鍔★紱鐢熸垚杩囪嚦灏戜竴涓繘浠撶紪鍙锋壒娆″悗缃负 2銆�
+        // status=1 鏄惎鍔ㄥ紑鍏筹紝pakoutOrderPause(execute=2) 浼氱疆 0 闃绘瀹氭椂鍣ㄧ户缁敓鎴愩��
+        order.setSettle(1L);
+        order.setStatus(1);
+        order.setCreateBy(9527L);
+        order.setCreateTime(now);
+        order.setUpdateBy(9527L);
+        order.setUpdateTime(now);
+        // moveStatus 淇濈暀鐜版湁鈥滃璐�/绉诲簱鈥濊涔夛紝杩欓噷涓嶅鐢ㄥ畠鍋氭殏鍋滃紑鍏炽��
+        order.setMoveStatus(0);
+        // 2 琛ㄧず鍑哄簱鏂瑰悜锛屽拰 man_order_detl_pakout 鏄庣粏淇濇寔涓�鑷淬��
+        order.setPakinPakoutStatus(2);
+        if (!orderPakoutService.insert(order)) {
+            throw new CoolException("鐢熸垚鍑哄簱璁㈠崟澶辫触锛�" + orderNo);
+        }
+        return order;
+    }
+
+    private void refreshPendingPakoutOrderForResubmit(OrderPakout order, Date now) {
+        if (order == null) {
+            return;
+        }
+        boolean hasDispatchedDetail = hasDispatchedPendingDetail(order.getId());
+        order.setStatus(1);
+        order.setSettle(hasDispatchedDetail ? 2L : 1L);
+        order.setUpdateBy(9527L);
+        order.setUpdateTime(now);
+        order.setPakinPakoutStatus(2);
+        if (!orderPakoutService.updateById(order)) {
+            throw new CoolException("鏇存柊鍑哄簱璁㈠崟澶辫触锛�" + order.getOrderNo());
+        }
+    }
+
+    private boolean hasDispatchedPendingDetail(Long orderId) {
+        List<OrderDetlPakout> details = orderDetlPakoutService.selectList(new EntityWrapper<OrderDetlPakout>()
+                .eq("order_id", orderId)
+                .eq("status", 1));
+        if (Cools.isEmpty(details)) {
+            return false;
+        }
+        for (OrderDetlPakout detail : details) {
+            if (!isUndispatchedPendingDetail(detail)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 鍒犻櫎鍚岃鍗曚腑灏氭湭涓嬪彂鐨勬槑缁嗐��
+     *
+     * 鍒ゆ柇鍙e緞锛�
+     * - work_qty <= 0锛氬皻鏈敓鎴愭湁鏁堜换鍔★紝鎴栦腑姝㈠悗浠诲姟宸茶鍙栨秷骞跺畬鎴愬洖婊氥��
+     * - qty <= 0锛氭病鏈変笟鍔″畬鎴愰噺锛屽垹闄ゅ悗涓嶄細涓㈠け宸插畬鎴愬嚭搴撹褰曘��
+     */
+    private int removeUndispatchedPendingDetails(Long orderId) {
+        List<OrderDetlPakout> details = orderDetlPakoutService.selectList(new EntityWrapper<OrderDetlPakout>()
+                .eq("order_id", orderId)
+                .eq("status", 1));
+        if (Cools.isEmpty(details)) {
+            return 0;
+        }
+        int removed = 0;
+        for (OrderDetlPakout detail : details) {
+            if (!isUndispatchedPendingDetail(detail)) {
+                continue;
+            }
+            if (!orderDetlPakoutService.deleteById(detail.getId())) {
+                throw new CoolException("鍒犻櫎鏈笅鍙戝嚭搴撹鍗曟槑缁嗗け璐ワ細" + detail.getOrderNo());
+            }
+            removed++;
+        }
+        return removed;
+    }
+
+    private void assertNoNonReplaceablePendingDetailConflict(OrderPakout order, List<OutTaskParam> params) {
+        if (order == null || Cools.isEmpty(params)) {
+            return;
+        }
+        Set<String> newPalletIds = params.stream()
+                .filter(Objects::nonNull)
+                .map(OutTaskParam::getPalletId)
+                .filter(palletId -> !Cools.isEmpty(palletId))
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        if (newPalletIds.isEmpty()) {
+            return;
+        }
+        List<OrderDetlPakout> details = orderDetlPakoutService.selectList(new EntityWrapper<OrderDetlPakout>()
+                .eq("order_id", order.getId())
+                .eq("status", 1));
+        if (Cools.isEmpty(details)) {
+            return;
+        }
+        for (OrderDetlPakout detail : details) {
+            if (detail == null || !newPalletIds.contains(detail.getPalletId()) || isUndispatchedPendingDetail(detail)) {
+                continue;
+            }
+            throw new CoolException("鎵樼洏銆�" + detail.getPalletId() + "銆嶅凡瀛樺湪宸蹭笅鍙戞垨宸插畬鎴愬嚭搴撴槑缁嗭紝鏃犳硶瑕嗙洊");
+        }
+    }
+
+    private boolean isUndispatchedPendingDetail(OrderDetlPakout detail) {
+        return detail != null
+                && safeDouble(detail.getWorkQty()) <= 0.0D
+                && safeDouble(detail.getQty()) <= 0.0D;
+    }
+
+    /**
+     * 鍒ゆ柇寤惰繜鍑哄簱璁㈠崟鏄惁杩樿兘琚綋鍓嶆帴鍙h姹傝鐩栥��
+     *
+     * 娲诲姩浠诲姟浠嶇劧瀛樺湪鏃朵笉鍏佽瑕嗙洊锛屽洜涓轰换鍔°�佸簱浣嶃�乄CS 鎸囦护杩樺湪鎵ц閾捐矾涓娿��
+     * 宸茬粡鍙栨秷骞跺綊妗e埌 WrkMastLog 鐨勪换鍔″厑璁搁噸涓嬪彂锛涘叾璁㈠崟鏄庣粏 work_qty 宸插湪鍙栨秷鏃跺洖婊氾紝
+     * 鏈鎺ュ彛浼氬垹闄ゆ湭涓嬪彂鏄庣粏骞舵彃鍏ユ柊鏄庣粏銆�
+     */
+    private void assertPendingPakoutOrderCanReplace(String orderNo) {
+        List<WrkMast> activeTasks = findActiveOutboundTasks(orderNo);
+        if (!Cools.isEmpty(activeTasks)) {
+            throw new CoolException("鍑哄簱璁㈠崟宸插瓨鍦ㄦ椿鍔ㄤ换鍔★紝鏃犳硶瑕嗙洊锛�" + orderNo);
+        }
+        int activeWrkCount = wrkMastService.selectCount(new EntityWrapper<WrkMast>()
+                .eq("io_type", 101)
+                .eq("user_no", orderNo));
+        if (activeWrkCount > 0) {
+            throw new CoolException("鍑哄簱璁㈠崟宸插瓨鍦ㄤ换鍔℃。锛屾棤娉曡鐩栵細" + orderNo);
+        }
+        OrderPakout order = orderPakoutService.selectByNo(orderNo);
+        if (order != null && order.getSettle() != null && order.getSettle() > 2L) {
+            throw new CoolException(orderNo + "姝e湪鍑哄簱锛屾棤娉曚慨鏀瑰崟鎹�");
+        }
+    }
+
+    /**
+     * 鎶� OutTaskParam 钀芥垚鍑哄簱璁㈠崟鏄庣粏銆�
+     *
+     * detail.sync(locDetl) 璐熻矗澶嶅埗搴撳瓨缁村害瀛楁锛堢墿鏂欍�佹壒娆°�佸搧鐗屻�佹墿灞曞瓧娈电瓑锛夛紱
+     * 鍚庣画 setXxx 淇濆瓨鎺ュ彛鍘熷瀛楁锛屼繚璇佸畾鏃跺櫒鍙互杩樺師瀹屾暣 OutTaskParam銆�
+     */
+    private OrderDetlPakout buildPendingPakoutOrderDetl(OrderPakout order, OutTaskParam param, Date now) {
+        LocDetl locDetl = locDetlService.selectOne(new EntityWrapper<LocDetl>().eq("zpallet", param.getPalletId()));
+        if (locDetl == null) {
+            throw new CoolException("搴撳瓨涓笉瀛樺湪璇ユ墭鐩橈細" + param.getPalletId());
+        }
+        if (!Cools.isEmpty(param.getMatnr()) && !Objects.equals(param.getMatnr(), locDetl.getMatnr())) {
+            throw new CoolException("鎵樼洏銆�" + param.getPalletId() + "銆嶇墿鏂欑紪鐮佷笌搴撳瓨涓嶄竴鑷�");
+        }
+        OrderDetlPakout detail = new OrderDetlPakout();
+        detail.sync(locDetl);
+        detail.setOrderId(order.getId());
+        detail.setOrderNo(order.getOrderNo());
+        detail.setAnfme(resolvePendingOrderAnfme(param, locDetl));
+        detail.setWorkQty(0.0D);
+        detail.setQty(0.0D);
+        detail.setStatus(1);
+        detail.setCreateBy(9527L);
+        detail.setCreateTime(now);
+        detail.setUpdateBy(9527L);
+        detail.setUpdateTime(now);
+        detail.setPakinPakoutStatus(2);
+        detail.setBatchSeq(param.getBatchSeq());
+        detail.setSeq(param.getSeq());
+        detail.setPalletId(param.getPalletId());
+        detail.setStationId(param.getStationId());
+        detail.setEntryWmsCode(param.getEntryWmsCode());
+        detail.setContainerNo(param.getContainerNo());
+        detail.setOutDoorNo(param.getOutDoorNo());
+        detail.setPlateNo(param.getPlateNo());
+        detail.setTrainNo(param.getTrainNo());
+        detail.setFreqType(param.getFreqType());
+        detail.setCubeNumber(param.getCubeNumber());
+        detail.setTeu(param.getTeu());
+        // standby1/standby2 缁х画闀滃儚杩涗粨缂栧彿鍜屽嚭搴撻棬鍙凤紝鍏煎鐜版湁瀹屾垚鍥炲啓鐨� selectItem 鍖归厤閫昏緫銆�
+        detail.setStandby1(param.getEntryWmsCode());
+        detail.setStandby2(param.getOutDoorNo());
+        return detail;
+    }
+
+    /**
+     * 鏄庣粏鏁伴噺浼樺厛浣跨敤 ERP 鍙傛暟涓殑 anfme銆�
+     * 濡傛灉 ERP 鏈紶鏁伴噺锛屽垯浣跨敤褰撳墠搴撳瓨鏁伴噺锛涗袱鑰呴兘涓嶅彲鐢ㄦ椂鎸夋暣鎵� 1 澶勭悊锛岄伩鍏嶇敓鎴� 0 鏁伴噺浠诲姟銆�
+     */
+    private double resolvePendingOrderAnfme(OutTaskParam param, LocDetl locDetl) {
+        if (param.getAnfme() > 0.0D) {
+            return param.getAnfme();
+        }
+        if (locDetl != null && locDetl.getAnfme() != null && locDetl.getAnfme() > 0.0D) {
+            return locDetl.getAnfme();
+        }
+        return 1.0D;
+    }
+
+    /**
+     * 鍒ゆ柇璁㈠崟鏄庣粏鏄惁杩橀渶瑕佺敓鎴愪换鍔°��
+     *
+     * work_qty 鏄换鍔$敓鎴愯繘搴︼紝qty 鏄换鍔″畬鎴愯繘搴︺��
+     * 杩欓噷鍙湅 anfme - work_qty锛岄伩鍏嶅畾鏃跺櫒鍥犱负浠诲姟鏈畬鎴愯�岄噸澶嶇敓鎴愬悓涓�鎵樼洏浠诲姟銆�
+     */
+    private boolean isPendingPakoutDetail(OrderDetlPakout detail) {
+        return detail != null
+                && isPendingOutOrderStation(detail.getStationId())
+                && !Cools.isEmpty(detail.getEntryWmsCode())
+                && getPendingDetailQty(detail) > 0.0D;
+    }
+
+    /**
+     * 浠庤鍗曟槑缁嗚繕鍘� outOrderBatch 闇�瑕佺殑鍙傛暟銆�
+     *
+     * batchSeq 蹇呴』寮哄埗鏀逛负 entryWmsCode锛岃�屼笉鏄部鐢� ERP 鍘熷 batchSeq锛�
+     * 杩欐牱鍚庣画 WCS 涓嬪彂瀹堝崼鎵嶈兘浠ヨ繘浠撶紪鍙蜂负椤哄簭杈圭晫銆�
+     */
+    private OutTaskParam buildOutTaskParam(String orderNo, String entryWmsCode, OrderDetlPakout detail) {
+        OutTaskParam param = new OutTaskParam();
+        param.setOrderId(orderNo);
+        param.setBatchSeq(entryWmsCode);
+        param.setSeq(detail.getSeq());
+        param.setPalletId(detail.getPalletId());
+        param.setStationId(detail.getStationId());
+        param.setMatnr(detail.getMatnr());
+        param.setAnfme(getPendingDetailQty(detail));
+        param.setEntryWmsCode(detail.getEntryWmsCode());
+        param.setContainerNo(detail.getContainerNo());
+        param.setOutDoorNo(detail.getOutDoorNo());
+        param.setPlateNo(detail.getPlateNo());
+        param.setTrainNo(detail.getTrainNo());
+        param.setFreqType(detail.getFreqType());
+        param.setCubeNumber(detail.getCubeNumber());
+        param.setTeu(detail.getTeu());
+        return param;
+    }
+
+    private Map<String, Object> buildGeneratePendingResult(String orderNo, String entryWmsCode, int generatedTaskCount, int pendingDetailCount, String reason) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("orderNo", orderNo);
+        result.put("entryWmsCode", entryWmsCode);
+        result.put("generatedTaskCount", generatedTaskCount);
+        result.put("pendingDetailCount", pendingDetailCount);
+        if (!Cools.isEmpty(reason)) {
+            result.put("reason", reason);
+        }
+        return result;
+    }
+
+    private boolean isPendingOutOrderStation(String stationId) {
+        if (Cools.isEmpty(stationId)) {
+            return false;
+        }
+        try {
+            return Integer.valueOf(stationId) > PENDING_OUT_ORDER_STATION_THRESHOLD;
+        } catch (NumberFormatException ignored) {
+            return false;
+        }
+    }
+
+    private double getPendingDetailQty(OrderDetlPakout detail) {
+        return safeDouble(detail.getAnfme()) - safeDouble(detail.getWorkQty());
+    }
+
+    private double safeDouble(Double value) {
+        return value == null ? 0.0D : value;
     }
 
     /**
@@ -1571,6 +2309,18 @@
 
     private String buildOutOrderBatchKey(OutTaskParam param) {
         return param.getOrderId() + "#" + param.getBatchSeq();
+    }
+
+    private Map<String, Integer> buildOutOrderBatchTeuCounts(List<OutTaskParam> params) {
+        Map<String, Set<String>> batchOrderIds = new HashMap<>();
+        for (OutTaskParam param : params) {
+            batchOrderIds.computeIfAbsent(param.getBatchSeq(), k -> new LinkedHashSet<>()).add(param.getOrderId());
+        }
+        Map<String, Integer> batchTeuCounts = new HashMap<>();
+        for (Map.Entry<String, Set<String>> entry : batchOrderIds.entrySet()) {
+            batchTeuCounts.put(entry.getKey(), entry.getValue().size());
+        }
+        return batchTeuCounts;
     }
 
     private String resolveOutboundOrderId(String palletId) {
@@ -1662,6 +2412,306 @@
         return response;
     }
 
+    private boolean isErpReportEnabled() {
+        if (!erpReportOld) {
+            return false;
+        }
+        String erpReport = Parameter.get().getErpReport();
+        return Cools.isEmpty(erpReport) || "true".equalsIgnoreCase(erpReport);
+    }
+
+    private List<String> normalizeBarcodes(List<String> barcodes) {
+        if (barcodes == null || barcodes.isEmpty()) {
+            return Collections.emptyList();
+        }
+        LinkedHashSet<String> normalized = new LinkedHashSet<>();
+        for (String barcode : barcodes) {
+            if (barcode == null) {
+                continue;
+            }
+            String value = barcode.trim();
+            if (!value.isEmpty()) {
+                normalized.add(value);
+            }
+        }
+        return new ArrayList<>(normalized);
+    }
+
+    private Map<String, WrkMastLog> loadLatestInboundHistoryLogMap(List<String> barcodes) {
+        Map<String, WrkMastLog> latestInboundLogMap = new LinkedHashMap<>();
+        if (barcodes == null || barcodes.isEmpty()) {
+            return latestInboundLogMap;
+        }
+        List<WrkMastLog> wrkMastLogs = wrkMastLogService.selectList(new EntityWrapper<WrkMastLog>()
+                .in("barcode", barcodes)
+                .orderBy("barcode", true)
+                .orderBy("io_time", false)
+                .orderBy("wrk_no", false));
+        if (Cools.isEmpty(wrkMastLogs)) {
+            return latestInboundLogMap;
+        }
+        for (WrkMastLog wrkMastLog : wrkMastLogs) {
+            if (wrkMastLog == null || Cools.isEmpty(wrkMastLog.getBarcode())) {
+                continue;
+            }
+            if (!isInboundHistoryLog(wrkMastLog.getIoType())) {
+                continue;
+            }
+            latestInboundLogMap.putIfAbsent(wrkMastLog.getBarcode(), wrkMastLog);
+        }
+        return latestInboundLogMap;
+    }
+
+    private boolean isInboundHistoryLog(Integer ioType) {
+        if (ioType == null) {
+            return false;
+        }
+        // 鍘嗗彶琛ㄩ噷鏃㈡湁鍏ュ簱瀹屾垚锛屼篃鏈夊簱瀛樿皟鏁达紱杩欓噷鍙彇鐪熸鐨勫叆搴撶被璁板綍銆�
+        return ioType < 19 || ioType == 53 || ioType == 54 || ioType == 57;
+    }
+
+    private Map<Integer, List<WrkDetlLog>> loadWrkDetlLogMap(Collection<WrkMastLog> wrkMastLogs) {
+        Map<Integer, List<WrkDetlLog>> wrkDetlLogMap = new HashMap<>();
+        if (wrkMastLogs == null || wrkMastLogs.isEmpty()) {
+            return wrkDetlLogMap;
+        }
+        LinkedHashSet<Integer> wrkNos = new LinkedHashSet<>();
+        for (WrkMastLog wrkMastLog : wrkMastLogs) {
+            if (wrkMastLog != null && wrkMastLog.getWrkNo() != null) {
+                wrkNos.add(wrkMastLog.getWrkNo());
+            }
+        }
+        if (wrkNos.isEmpty()) {
+            return wrkDetlLogMap;
+        }
+        List<WrkDetlLog> wrkDetlLogs = wrkDetlLogService.selectList(new EntityWrapper<WrkDetlLog>().in("wrk_no", wrkNos));
+        if (Cools.isEmpty(wrkDetlLogs)) {
+            return wrkDetlLogMap;
+        }
+        for (WrkDetlLog wrkDetlLog : wrkDetlLogs) {
+            if (wrkDetlLog == null || wrkDetlLog.getWrkNo() == null) {
+                continue;
+            }
+            wrkDetlLogMap.computeIfAbsent(wrkDetlLog.getWrkNo(), k -> new ArrayList<>()).add(wrkDetlLog);
+        }
+        return wrkDetlLogMap;
+    }
+
+    private Map<String, WaitPakinLog> loadWaitPakinLogMap(List<String> barcodes) {
+        Map<String, WaitPakinLog> waitPakinLogMap = new LinkedHashMap<>();
+        if (barcodes == null || barcodes.isEmpty()) {
+            return waitPakinLogMap;
+        }
+        List<WaitPakinLog> waitPakinLogs = waitPakinLogService.selectList(new EntityWrapper<WaitPakinLog>()
+                .in("zpallet", barcodes)
+                .orderBy("zpallet", true)
+                .orderBy("appe_time", false)
+                .orderBy("modi_time", false));
+        if (Cools.isEmpty(waitPakinLogs)) {
+            return waitPakinLogMap;
+        }
+        for (WaitPakinLog waitPakinLog : waitPakinLogs) {
+            if (waitPakinLog == null || Cools.isEmpty(waitPakinLog.getZpallet())) {
+                continue;
+            }
+            waitPakinLogMap.putIfAbsent(waitPakinLog.getZpallet(), waitPakinLog);
+        }
+        return waitPakinLogMap;
+    }
+
+    private ErpPakinReportParam buildInboundErpParam(String barcode,
+                                                     WrkMastLog wrkMastLog,
+                                                     List<WrkDetlLog> wrkDetlLogs,
+                                                     WaitPakinLog waitPakinLog) {
+        ErpPakinReportParam param = new ErpPakinReportParam();
+        param.setPalletId(resolvePalletId(barcode, wrkMastLog, wrkDetlLogs, waitPakinLog));
+        param.setAnfme(resolveInboundAnfme(wrkDetlLogs, waitPakinLog));
+        param.setLocId(transformInboundLocId(resolveInboundLocNo(wrkMastLog, waitPakinLog)));
+        param.setWeight(resolveInboundWeight(wrkMastLog, waitPakinLog));
+        param.setCreateTime(formatDate(resolveInboundCreateTime(wrkMastLog)));
+        param.setBizNo(resolveInboundBizNo(wrkDetlLogs, waitPakinLog));
+        param.setStartTime(formatDate(resolveInboundStartTime(wrkMastLog, waitPakinLog)));
+        return param;
+    }
+
+    private String resolvePalletId(String barcode,
+                                   WrkMastLog wrkMastLog,
+                                   List<WrkDetlLog> wrkDetlLogs,
+                                   WaitPakinLog waitPakinLog) {
+        if (wrkMastLog != null && !Cools.isEmpty(wrkMastLog.getBarcode())) {
+            return wrkMastLog.getBarcode();
+        }
+        if (wrkDetlLogs != null) {
+            for (WrkDetlLog wrkDetlLog : wrkDetlLogs) {
+                if (wrkDetlLog != null && !Cools.isEmpty(wrkDetlLog.getZpallet())) {
+                    return wrkDetlLog.getZpallet();
+                }
+            }
+        }
+        if (waitPakinLog != null && !Cools.isEmpty(waitPakinLog.getZpallet())) {
+            return waitPakinLog.getZpallet();
+        }
+        return barcode;
+    }
+
+    private Double resolveInboundAnfme(List<WrkDetlLog> wrkDetlLogs, WaitPakinLog waitPakinLog) {
+        double total = 0D;
+        boolean hasDetail = false;
+        if (wrkDetlLogs != null) {
+            for (WrkDetlLog wrkDetlLog : wrkDetlLogs) {
+                if (wrkDetlLog == null || wrkDetlLog.getAnfme() == null) {
+                    continue;
+                }
+                total += wrkDetlLog.getAnfme();
+                hasDetail = true;
+            }
+        }
+        if (hasDetail) {
+            return total;
+        }
+        if (waitPakinLog != null && waitPakinLog.getAnfme() != null) {
+            return waitPakinLog.getAnfme();
+        }
+        return total;
+    }
+
+    private BigDecimal resolveInboundWeight(WrkMastLog wrkMastLog, WaitPakinLog waitPakinLog) {
+        BigDecimal baseWeight = BigDecimal.ZERO;
+        if (wrkMastLog != null && wrkMastLog.getScWeight() != null) {
+            baseWeight = BigDecimal.valueOf(wrkMastLog.getScWeight());
+        } else if (waitPakinLog != null && waitPakinLog.getWeight() != null) {
+            baseWeight = BigDecimal.valueOf(waitPakinLog.getWeight());
+        }
+        Integer sourceStaNo = resolveInboundSourceStaNo(wrkMastLog);
+        return baseWeight.multiply(resolveInboundWeightFactor(sourceStaNo));
+    }
+
+    private Integer resolveInboundSourceStaNo(WrkMastLog wrkMastLog) {
+        if (wrkMastLog == null) {
+            return null;
+        }
+        if (wrkMastLog.getSourceStaNo() != null) {
+            return wrkMastLog.getSourceStaNo();
+        }
+        return wrkMastLog.getStaNo();
+    }
+
+    private BigDecimal resolveInboundWeightFactor(Integer sourceStaNo) {
+        if (sourceStaNo == null) {
+            return BigDecimal.ONE;
+        }
+        BigDecimal factor = INBOUND_WEIGHT_FACTOR_BY_SOURCE_STA.get(sourceStaNo);
+        return factor == null ? BigDecimal.ONE : factor;
+    }
+
+    private String resolveInboundLocNo(WrkMastLog wrkMastLog, WaitPakinLog waitPakinLog) {
+        if (wrkMastLog != null && !Cools.isEmpty(wrkMastLog.getLocNo())) {
+            return wrkMastLog.getLocNo();
+        }
+        if (waitPakinLog != null && !Cools.isEmpty(waitPakinLog.getLocNo())) {
+            return waitPakinLog.getLocNo();
+        }
+        return null;
+    }
+
+    private Date resolveInboundCreateTime(WrkMastLog wrkMastLog) {
+        if (wrkMastLog == null) {
+            return new Date();
+        }
+        if (wrkMastLog.getCrnEndTime() != null) {
+            return wrkMastLog.getCrnEndTime();
+        }
+        if (wrkMastLog.getModiTime() != null) {
+            return wrkMastLog.getModiTime();
+        }
+        if (wrkMastLog.getIoTime() != null) {
+            return wrkMastLog.getIoTime();
+        }
+        return new Date();
+    }
+
+    private Date resolveInboundStartTime(WrkMastLog wrkMastLog, WaitPakinLog waitPakinLog) {
+        if (waitPakinLog != null && waitPakinLog.getAppeTime() != null) {
+            return waitPakinLog.getAppeTime();
+        }
+        if (wrkMastLog == null) {
+            return new Date();
+        }
+        if (wrkMastLog.getAppeTime() != null) {
+            return wrkMastLog.getAppeTime();
+        }
+        if (wrkMastLog.getIoTime() != null) {
+            return wrkMastLog.getIoTime();
+        }
+        if (wrkMastLog.getModiTime() != null) {
+            return wrkMastLog.getModiTime();
+        }
+        return new Date();
+    }
+
+    private String resolveInboundBizNo(List<WrkDetlLog> wrkDetlLogs, WaitPakinLog waitPakinLog) {
+        if (wrkDetlLogs != null) {
+            for (WrkDetlLog wrkDetlLog : wrkDetlLogs) {
+                if (wrkDetlLog != null && !Cools.isEmpty(wrkDetlLog.getThreeCode())) {
+                    return wrkDetlLog.getThreeCode();
+                }
+            }
+        }
+        if (waitPakinLog != null && !Cools.isEmpty(waitPakinLog.getThreeCode())) {
+            return waitPakinLog.getThreeCode();
+        }
+        return null;
+    }
+
+    private String transformInboundLocId(String locId) {
+        if (Cools.isEmpty(locId)) {
+            return null;
+        }
+        String trimmed = locId.trim();
+        if (trimmed.length() < 7) {
+            return trimmed;
+        }
+        String row = trimmed.substring(0, 2);
+        String col = trimmed.substring(2, 5);
+        String lev = trimmed.substring(5, 7);
+        try {
+            int rowNo = Integer.parseInt(row);
+            if (rowNo >= 37) {
+                row = "C" + row;
+            } else if (rowNo >= 13) {
+                row = "B" + row;
+            } else {
+                row = "A" + row;
+            }
+        } catch (Exception ignore) {
+            return trimmed;
+        }
+        return row + "-" + col + "-" + lev;
+    }
+
+    private String formatDate(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return new SimpleDateFormat(DATE_TIME_PATTERN).format(date);
+    }
+
+    private String buildErpInboundRequestUrl() {
+        if (Cools.isEmpty(erpUrl)) {
+            return erpInAddress;
+        }
+        if (erpInAddress == null) {
+            return erpUrl;
+        }
+        if (erpUrl.endsWith("/") && erpInAddress.startsWith("/")) {
+            return erpUrl + erpInAddress.substring(1);
+        }
+        if (!erpUrl.endsWith("/") && !erpInAddress.startsWith("/")) {
+            return erpUrl + "/" + erpInAddress;
+        }
+        return erpUrl + erpInAddress;
+    }
+
     private String buildErpOutErrorRequestUrl() {
         if (Cools.isEmpty(erpUrl)) {
             return erpOutErrorAddress;

--
Gitblit v1.9.1