pan
2025-11-21 482aa82a99419383848cabbdf135744259b17c77
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -1,6 +1,7 @@
using Dm.filter;
using MailKit.Search;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using SqlSugar;
using System;
@@ -208,7 +209,7 @@
                                details = new List<FeedbackOutboundDetailsModel>()
                            };
                            var lists = _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>().Where(x => x.OrderNo == orderNo).ToList();
                            var lists = _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>().Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
                            var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                               .Select(group => new FeedbackOutboundDetailsModel
@@ -225,13 +226,21 @@
                                       supplyCode = row.SupplyCode,
                                       batchNo = row.BatchNo,
                                       unit = row.Unit,
                                       qty = row.AssignQuantity
                                       qty = row.PickedQty
                                   }).ToList()
                               }).ToList();
                            feedmodel.details = groupedData;
                            _invokeMESService.FeedbackOutbound(feedmodel);
                            var result = await _invokeMESService.FeedbackOutbound(feedmodel);
                            if (result != null && result.code == 200)
                            {
                                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>().SetColumns(x => x.ReturnToMESStatus == 1) // å·²å®Œæˆ
                                            .Where(x => x.OrderId == outboundOrder.Id).ExecuteCommandAsync();
                                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>() .SetColumns(x => x.ReturnToMESStatus == 1) // å·²å®Œæˆ
                                              .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync();
                            }
                        }
                    }
                }
                catch (Exception ex)
@@ -248,50 +257,102 @@
            {
                _unitOfWorkManage.BeginTran();
                // 1. æŸ¥æ‰¾å‡ºåº“锁定信息
                // 1. éªŒè¯è¾“入参数
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
                {
                    throw new Exception("订单号、托盘码和条码不能为空");
                }
                // 2. æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.Status == (int)OutLockStockStatusEnum.出库中 &&
                               it.PalletCode == palletCode &&
                               it.CurrentBarcode == barcode)
                               it.CurrentBarcode == barcode &&
                               it.AssignQuantity > it.PickedQty)
                    .FirstAsync();
                if (lockInfo == null)
                {
                    lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
    .Where(it => it.CurrentBarcode == barcode &&
               it.Status == (int)OutLockStockStatusEnum.出库中)
    .FirstAsync();
                        .Where(it => it.CurrentBarcode == barcode &&
                                   it.Status == (int)OutLockStockStatusEnum.出库中 &&
                                   it.AssignQuantity > it.PickedQty)
                        .FirstAsync();
                    if (lockInfo == null)
                        throw new Exception($"条码{barcode}不属于托盘{palletCode}或不存在待分拣记录");
                    {
                        var completedLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                            .Where(it => it.CurrentBarcode == barcode &&
                                       (it.Status == (int)OutLockStockStatusEnum.拣选完成 ||
                                        it.PickedQty >= it.AssignQuantity))
                            .FirstAsync();
                        if (completedLockInfo != null)
                            throw new Exception($"条码{barcode}已经完成分拣,不能重复分拣");
                        else
                            throw new Exception($"条码{barcode}不属于托盘{palletCode}或不存在待分拣记录");
                    }
                }
                if (lockInfo.PalletCode != palletCode)
                    throw new Exception($"条码{barcode}不属于托盘{palletCode}");
                var outorderdetail = _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().First(x => x.Id == lockInfo.OrderDetailId);
                if (outorderdetail != null && lockInfo.AssignQuantity > outorderdetail.OrderQuantity)
                // æ£€æŸ¥æ‹£é€‰åŽ†å²ï¼Œé˜²æ­¢é‡å¤åˆ†æ‹£
                var existingPicking = await Db.Queryable<Dt_PickingRecord>()
                    .Where(x => x.Barcode == barcode &&
                               x.OrderNo == orderNo &&
                               x.PalletCode == palletCode &&
                               x.OutStockLockId == lockInfo.Id)
                    .FirstAsync();
                if (existingPicking != null)
                {
                    throw new Exception($"条码{barcode}的出库数量大于订单的数量");
                    throw new Exception($"条码{barcode}已经分拣过,不能重复分拣");
                }
                // èŽ·å–è®¢å•æ˜Žç»†å¹¶æ£€æŸ¥æ•°é‡é™åˆ¶
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
                if (orderDetail == null)
                    throw new Exception($"未找到订单明细,ID: {lockInfo.OrderDetailId}");
                // å…³é”®ä¿®å¤ï¼šæ£€æŸ¥ç´¯è®¡æ‹£é€‰æ•°é‡æ˜¯å¦ä¼šè¶…过订单数量
                decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                decimal remainingOrderQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
                if (actualQty > remainingOrderQty)
                {
                    // å¦‚果分配数量大于剩余订单数量,调整实际拣选数量
                    actualQty = remainingOrderQty;
                    if (actualQty <= 0)
                    {
                        throw new Exception($"订单{orderNo}的需求数量已满足,无法继续分拣");
                    }
                    _logger.LogWarning($"调整分拣数量:原分配{lockInfo.AssignQuantity - lockInfo.PickedQty},调整为{actualQty},订单需求{orderDetail.NeedOutQuantity},已出库{orderDetail.OverOutQuantity}");
                }
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
                        .FirstAsync();
                    .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
                    .FirstAsync();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                decimal stockQuantity = stockDetail.StockQuantity;
                List<SplitResult> splitResults = new List<SplitResult>();
                Dt_OutStockLockInfo finalLockInfo = lockInfo;
                var finalBarcode = barcode;
                var finalStockId = stockDetail.Id;
                decimal actualPickedQty = actualQty;
                if (actualQty < stockQuantity)
                {
                    // æƒ…况1: åˆ†é…æ•°é‡å°äºŽåº“存数量,需要自动拆包
                    // è®¡ç®—剩余库存数量
                    decimal remainingStockQty = stockQuantity - actualQty;
                    // æ›´æ–°åŽŸæ¡ç åº“å­˜ä¸ºå‰©ä½™æ•°é‡
@@ -299,11 +360,11 @@
                    stockDetail.OutboundQuantity = remainingStockQty;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    // ç”Ÿæˆæ–°æ¡ç ç”¨äºŽè®°å½•拣选数量(但不创建库存记录)
                    // ç”Ÿæˆæ–°æ¡ç ç”¨äºŽè®°å½•拣选数量
                    var seq = await _dailySequenceService.GetNextSequenceAsync();
                    string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
                    // ä¸ºæ–°æ¡ç åˆ›å»ºå‡ºåº“锁定信息(用于记录拣选)
                    // ä¸ºæ–°æ¡ç åˆ›å»ºå‡ºåº“锁定信息
                    var newLockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = lockInfo.OrderNo,
@@ -328,9 +389,11 @@
                        IsSplitted = 1,
                        ParentLockId = lockInfo.Id
                    };
                    await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
                    // è®°å½•拆包历史(用于追踪)
                    var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync();
                    newLockInfo.Id = newLockId;
                    // è®°å½•拆包历史
                    var splitHistory = new Dt_SplitPackageRecord
                    {
                        FactoryArea = lockInfo.FactoryArea,
@@ -353,21 +416,33 @@
                    // æ›´æ–°åŽŸé”å®šä¿¡æ¯ä¸ºå‰©ä½™åº“å­˜æ•°é‡
                    lockInfo.AssignQuantity = remainingStockQty;
                    lockInfo.PickedQty = 0;
                    lockInfo.PickedQty = 0;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    splitResults.Add(new SplitResult
                    {
                        OriginalBarcode = barcode,
                        NewBarcode = newBarcode,
                        SplitQuantity = actualQty,
                        RemainQuantity = remainingStockQty
                        materialCode = lockInfo.MaterielCode,
                        supplierCode = lockInfo.SupplyCode,
                        quantityTotal = actualQty.ToString("F2"),
                        batchNumber = newBarcode,
                        batch = lockInfo.BatchNo,
                        factory = lockInfo.FactoryArea,
                        date = DateTime.Now.ToString("yyyy-MM-dd"),
                    });
                    // æ›´æ–°æ‹£é€‰è®°å½•中的条码为新条码
                    barcode = newBarcode;
                    lockInfo = newLockInfo;
                    splitResults.Add(new SplitResult
                    {
                        materialCode = lockInfo.MaterielCode,
                        supplierCode = lockInfo.SupplyCode,
                        quantityTotal = remainingStockQty.ToString("F2"),
                        batchNumber = barcode,
                        batch = lockInfo.BatchNo,
                        factory = lockInfo.FactoryArea,
                        date = DateTime.Now.ToString("yyyy-MM-dd"),
                    });
                    finalLockInfo = newLockInfo;
                    finalBarcode = newBarcode;
                }
                else if (actualQty == stockQuantity)
                {
@@ -383,370 +458,1005 @@
                else
                {
                    // æƒ…况3: åˆ†é…æ•°é‡å¤§äºŽåº“存数量,库存整包出库
                    // æ•´åŒ…出库当前库存
                    decimal stockOutQty = stockQuantity;
                    stockDetail.StockQuantity = 0;
                    stockDetail.OutboundQuantity = 0;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    // è®¡ç®—剩余分配数量
                    decimal remainingAssignQty = actualQty - stockQuantity;
                    // æ›´æ–°é”å®šä¿¡æ¯ï¼ˆåªå®Œæˆåº“存部分)
                    lockInfo.PickedQty += stockOutQty;
                    lockInfo.AssignQuantity = remainingAssignQty;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    var _relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
        .Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode)
        .Where(it => !it.IsReverted)
        .ToListAsync();
                    var relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                        .Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode)
                        .Where(it => !it.IsReverted)
                        .ToListAsync();
                    foreach (var record in _relatedSplitRecords)
                    foreach (var record in relatedSplitRecords)
                    {
                        record.Status = (int)SplitPackageStatusEnum.已拣选;
                        await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                    }
                    actualPickedQty = stockOutQty;
                }
                // å…³é”®ä¿®å¤ï¼šå†æ¬¡æ£€æŸ¥è®¢å•数量限制
                decimal newOverOutQuantity = orderDetail.OverOutQuantity + actualPickedQty;
                decimal newPickedQty = orderDetail.PickedQty + actualPickedQty;
                if (newOverOutQuantity > orderDetail.NeedOutQuantity)
                {
                    throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({orderDetail.NeedOutQuantity})");
                }
                // æ›´æ–°è®¢å•明细
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => it.PickedQty == it.PickedQty + actualQty)
                    .SetColumns(it => new Dt_OutboundOrderDetail
                    {
                        PickedQty = newPickedQty,
                        OverOutQuantity = newOverOutQuantity
                    })
                    .Where(it => it.Id == lockInfo.OrderDetailId)
                    .ExecuteCommandAsync();
                await CheckAndUpdateOrderStatus(orderNo);
                // æŸ¥è¯¢ä»»åŠ¡è¡¨
                // æŸ¥è¯¢ä»»åŠ¡è¡¨
                var task = _taskRepository.QueryData(x => x.OrderNo == orderNo && x.PalletCode == palletCode).FirstOrDefault();
                if (finalLockInfo.Id <= 0)
                {
                    throw new Exception($"锁定信息ID无效: {finalLockInfo.Id},无法记录拣选历史");
                }
                // è®°å½•拣选历史
                var pickingHistory = new Dt_PickingRecord
                {
                    FactoryArea = lockInfo.FactoryArea,
                    FactoryArea = finalLockInfo.FactoryArea,
                    TaskNo = task?.TaskNum ?? 0,
                    LocationCode = task?.SourceAddress ?? "",
                    StockId = stockDetail.Id,
                    StockId = finalStockId,
                    OrderNo = orderNo,
                    OrderDetailId = lockInfo.OrderDetailId,
                    OrderDetailId = finalLockInfo.OrderDetailId,
                    PalletCode = palletCode,
                    Barcode = barcode,
                    MaterielCode = lockInfo.MaterielCode,
                    PickQuantity = actualQty,
                    Barcode = finalBarcode,
                    MaterielCode = finalLockInfo.MaterielCode,
                    PickQuantity = actualPickedQty,
                    PickTime = DateTime.Now,
                    Operator = App.User.UserName,
                    OutStockLockId = lockInfo.Id
                    OutStockLockId = finalLockInfo.Id
                };
                await Db.Insertable(pickingHistory).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                // å¦‚果有拆包结果,返回拆包信息
                if (splitResults.Any())
                {
                    return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = splitResults });
                }
                return WebResponseContent.Instance.OK("拣选确认成功");
                return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"ConfirmPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        /// <summary>
        /// å›žåº“操作
        /// </summary>
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾æ‹£é€‰è®°å½•
                var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.PalletCode == palletCode &&
                               it.Barcode == barcode)
                    .OrderByDescending(it => it.PickTime)
                    .FirstAsync();
                if (pickingRecord == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选记录");
                // æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.Id == pickingRecord.OutStockLockId)
                    .FirstAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的出库锁定信息");
                // æ£€æŸ¥æ˜¯å¦å¯ä»¥å–消
                if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                    return WebResponseContent.Instance.Error("当前状态不允许取消分拣");
                decimal cancelQty = pickingRecord.PickQuantity;
                // èŽ·å–è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
                if (orderDetail == null)
                    throw new Exception($"未找到订单明细,ID: {pickingRecord.OrderDetailId}");
                // å…³é”®ä¿®å¤ï¼šæ£€æŸ¥å–消后数量不会为负数
                decimal newOverOutQuantity = orderDetail.OverOutQuantity - cancelQty;
                decimal newPickedQty = orderDetail.PickedQty - cancelQty;
                if (newOverOutQuantity < 0 || newPickedQty < 0)
                {
                    throw new Exception($"取消分拣将导致已出库数量或已拣选数量为负数");
                }
                // å¤„理取消逻辑
                if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
                {
                    await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
                }
                else
                {
                    await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty);
                }
                // æ›´æ–°è®¢å•明细
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => new Dt_OutboundOrderDetail
                    {
                        PickedQty = newPickedQty,
                        OverOutQuantity = newOverOutQuantity
                    })
                    .Where(it => it.Id == pickingRecord.OrderDetailId)
                    .ExecuteCommandAsync();
                // åˆ é™¤æ‹£é€‰è®°å½•
                await Db.Deleteable<Dt_PickingRecord>()
                    .Where(x => x.Id == pickingRecord.Id)
                    .ExecuteCommandAsync();
                // é‡æ–°æ£€æŸ¥è®¢å•状态
                await CheckAndUpdateOrderStatus(orderNo);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{cancelQty}");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"CancelPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        private async Task HandleSplitBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            // æŸ¥æ‰¾çˆ¶é”å®šä¿¡æ¯
            var parentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == lockInfo.ParentLockId.Value)
                .FirstAsync();
            if (parentLockInfo == null)
            {
                throw new Exception("未找到父锁定信息,无法取消拆包分拣");
            }
            // æ¢å¤çˆ¶é”å®šä¿¡æ¯çš„分配数量
            parentLockInfo.AssignQuantity += cancelQty;
            await _outStockLockInfoService.Db.Updateable(parentLockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == parentLockInfo.CurrentBarcode && x.StockId == parentLockInfo.StockId)
                .FirstAsync();
            if (stockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
            // æ›´æ–°æ‹†åŒ…记录状态
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已撤销,
                    IsReverted = true
                })
                .Where(x => x.NewBarcode == lockInfo.CurrentBarcode && !x.IsReverted)
                .ExecuteCommandAsync();
            // åˆ é™¤æ‹†åŒ…产生的锁定信息
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == lockInfo.Id)
                .ExecuteCommandAsync();
        }
        private async Task HandleNormalBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            // æ¢å¤é”å®šä¿¡æ¯
            lockInfo.PickedQty -= cancelQty;
            if (lockInfo.PickedQty < 0) lockInfo.PickedQty = 0;
            lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == pickingRecord.Barcode && x.StockId == pickingRecord.StockId)
                .FirstAsync();
            if (stockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
        }
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
                {
                    return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
                }
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .FirstAsync(x => x.PalletCode == palletCode);
                // èŽ·å–æ‰€æœ‰æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•ï¼ŒåŒ…æ‹¬æ‹†åŒ…äº§ç”Ÿçš„è®°å½•
                var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo && it.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
                var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
                var tasks = new List<Dt_Task>();
                // æŸ¥è¯¢ä»»åŠ¡è¡¨
                var task = remainingLocks.Any()
                    ? _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault()
                    : _taskRepository.QueryData(x => x.PalletCode == palletCode).FirstOrDefault();
                if (stockInfo == null)
                {
                    return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} å¯¹åº”的库存信息");
                }
                var task = await GetCurrentTask(orderNo, palletCode);
                if (task == null)
                {
                    return WebResponseContent.Instance.Error("未找到对应的任务信息");
                }
                // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–非出库货物(库存货物)
                var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(it => it.StockId == stockinfo.Id &&
                                (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
                                 it.Status == StockStatusEmun.入库完成.ObjToInt() ||
                                 it.Status == StockStatusEmun.出库锁定.ObjToInt()))
                    .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // æœªå®Œå…¨å‡ºåº“çš„
                    .ToListAsync();
                // åˆ†æžéœ€è¦å›žåº“的货物
                var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id);
                // æ£€æŸ¥æ‹†åŒ…记录,找出需要回库的条码
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                    .ToListAsync();
                // è®¡ç®—需要回库的拆包条码
                var splitBarcodesToReturn = new List<string>();
                foreach (var splitRecord in splitRecords)
                if (!returnAnalysis.HasItemsToReturn)
                {
                    // æ£€æŸ¥åŽŸæ¡ç æ˜¯å¦è¿˜æœ‰åº“å­˜éœ€è¦å›žåº“
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockinfo.Id)
                        .FirstAsync();
                    if (originalStock != null && originalStock.StockQuantity > 0)
                    {
                        splitBarcodesToReturn.Add(splitRecord.OriginalBarcode);
                    }
                    // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦è¿˜æœ‰åº“存需要回库
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockinfo.Id)
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        splitBarcodesToReturn.Add(splitRecord.NewBarcode);
                    }
                    return await HandleNoReturnItems(orderNo, palletCode);
                }
                // å¦‚果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物,也无拆包剩余货物)
                if (!remainingLocks.Any() && !palletStockGoods.Any() && !splitBarcodesToReturn.Any())
                {
                    // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
                    var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
                        .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
                // æ‰§è¡Œå›žåº“操作
                await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, returnAnalysis);
                    if (allPicked)
                    {
                        return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
                    }
                    else
                    {
                        return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
                    }
                }
                //创建回库任务并处理ESS
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, returnAnalysis);
                var firstlocation = _locationInfoService.Db.Queryable<Dt_LocationInfo>().First(x => x.LocationCode == task.SourceAddress);
                decimal totalReturnQty = 0;
                _unitOfWorkManage.CommitTran();
                // æƒ…况1:处理未分拣的出库锁定记录
                if (remainingLocks.Any(x => x.PalletCode == palletCode))
                {
                    var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
                    totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                // æ›´æ–°è®¢å•状态
                await UpdateOrderStatusForReturn(orderNo);
                    if (totalReturnQty > 0)
                    {
                        // åˆ†é…æ–°è´§ä½
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{returnAnalysis.TotalReturnQty}");
                        // æ›´æ–°å‡ºåº“锁定记录状态
                        var lockIds = palletLocks.Select(x => x.Id).ToList();
                        await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                            .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.回库中 })
                            .Where(it => lockIds.Contains(it.Id))
                            .ExecuteCommandAsync();
                        // å¤„理库存记录
                        foreach (var lockInfo in palletLocks)
                        {
                            decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                            // æ£€æŸ¥åº“存记录是否存在
                            var existingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                                .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                                .FirstAsync();
                            if (existingStock != null)
                            {
                                // åº“存记录存在,恢复锁定数量
                                existingStock.OutboundQuantity = 0;
                                await _stockInfoDetailService.Db.Updateable(existingStock).ExecuteCommandAsync();
                            }
                            else
                            {
                                // åº“存记录不存在(可能是拆包产生的新条码),创建新的库存记录
                                var newStockDetail = new Dt_StockInfoDetail
                                {
                                    StockId = lockInfo.StockId,
                                    MaterielCode = lockInfo.MaterielCode,
                                    MaterielName = lockInfo.MaterielName,
                                    OrderNo = lockInfo.OrderNo,
                                    BatchNo = lockInfo.BatchNo,
                                    StockQuantity = returnQty,
                                    OutboundQuantity = 0,
                                    Barcode = lockInfo.CurrentBarcode,
                                    InboundOrderRowNo = "",
                                    Status = StockStatusEmun.入库完成.ObjToInt(),
                                    SupplyCode = lockInfo.SupplyCode,
                                    WarehouseCode = lockInfo.WarehouseCode,
                                    Unit = lockInfo.Unit
                                };
                                await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                            }
                        }
                        // åˆ›å»ºå›žåº“任务
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                // æƒ…况2:处理拆包剩余的库存货物
                if (splitBarcodesToReturn.Any())
                {
                    decimal splitReturnQty = 0;
                    foreach (var barcode in splitBarcodesToReturn)
                    {
                        var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                            .Where(it => it.Barcode == barcode && it.StockId == stockinfo.Id)
                            .FirstAsync();
                        if (stockDetail != null && stockDetail.StockQuantity > 0)
                        {
                            splitReturnQty += stockDetail.StockQuantity;
                            // æ¢å¤åº“存状态为入库完成
                            stockDetail.OutboundQuantity = 0;
                            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                        }
                    }
                    totalReturnQty += splitReturnQty;
                    // å¦‚果没有创建任务,创建回库任务
                    if (!tasks.Any())
                    {
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                // æƒ…况3:出库货物已分拣完,但托盘上还有其他库存货物需要回库
                if (palletStockGoods.Any() && !remainingLocks.Any(x => x.PalletCode == palletCode))
                {
                    decimal otherReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
                    totalReturnQty += otherReturnQty;
                    // æ›´æ–°è¿™äº›åº“存货物的状态
                    foreach (var stockGood in palletStockGoods)
                    {
                        stockGood.OutboundQuantity = 0;
                        await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
                    }
                    // å¦‚果没有创建任务,创建回库任务
                    if (!tasks.Any())
                    {
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                var allSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                    .ToListAsync();
                foreach (var record in allSplitRecords)
                {
                    record.Status = (int)SplitPackageStatusEnum.已回库;
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                // ä¿å­˜ä»»åŠ¡ ç»™ESS下发任务
                if (tasks.Any())
                {
                    try
                    {
                        await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
                        var targetAddress = task.TargetAddress;
                        _taskRepository.DeleteData(task);
                        // ç»™ ESS æµåŠ¨ä¿¡å·å’Œåˆ›å»ºä»»åŠ¡
                        try
                        {
                            var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                            {
                                slotCode = movestations[targetAddress],
                                containerCode = palletCode
                            });
                            if (result)
                            {
                                TaskModel esstask = new TaskModel()
                                {
                                    taskType = "putaway",
                                    taskGroupCode = "",
                                    groupPriority = 0,
                                    tasks = new List<TasksType>
                            {
                                new()
                                {
                                    taskCode = tasks.First().TaskNum.ToString(),
                                    taskPriority = 0,
                                    taskDescribe = new TaskDescribeType {
                                        containerCode = palletCode,
                                        containerType = "CT_KUBOT_STANDARD",
                                        fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
                                        toStationCode = "",
                                        toLocationCode = tasks.First().TargetAddress,
                                        deadline = 0, storageTag = ""
                                    }
                                }
                            }
                                };
                                var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
                                _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž:  " + resulttask);
                            }
                        }
                        catch (Exception ex)
                        {
                            _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž catch err:  " + ex.Message);
                        }
                        _unitOfWorkManage.CommitTran();
                        return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
                    }
                    catch (Exception ex)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
                    }
                }
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error("未创建任何回库任务");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"ReturnRemaining失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
            }
        }
        #region è®¢å•状态
        private async Task UpdateOrderStatusForReturn(string orderNo)
        {
            try
            {
                var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                    .Where((o, item) => item.OrderNo == orderNo)
                    .Select((o, item) => o)
                    .ToListAsync();
                bool allCompleted = true;
                foreach (var detail in orderDetails)
                {
                    if (detail.OverOutQuantity < detail.NeedOutQuantity)
                    {
                        allCompleted = false;
                        break;
                    }
                }
                var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                    .FirstAsync(x => x.OrderNo == orderNo);
                if (outboundOrder == null) return;
                // åªæœ‰å½“状态确实发生变化时才更新
                int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    _logger.LogInformation($"回库操作更新订单状态 - OrderNo: {orderNo}, æ–°çŠ¶æ€: {newStatus}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"UpdateOrderStatusForReturn失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
        }
        #endregion
        #region Private Methods
        private async Task<Dt_Task> GetCurrentTask(string orderNo, string palletCode)
        {
            // å…ˆå°è¯•通过订单号和托盘号查找任务
            var task = await _taskRepository.Db.Queryable<Dt_Task>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .FirstAsync();
            if (task == null)
            {
                // å¦‚果找不到,再通过托盘号查找
                task = await _taskRepository.Db.Queryable<Dt_Task>()
                    .Where(x => x.PalletCode == palletCode)
                    .FirstAsync();
            }
            return task;
        }
        private async Task<ReturnAnalysisResult> AnalyzeReturnItems(string orderNo, string palletCode, int stockId)
        {
            var result = new ReturnAnalysisResult();
            // èŽ·å–æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•
            var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
                           it.Status == (int)OutLockStockStatusEnum.出库中)
                .ToListAsync();
            if (remainingLocks.Any())
            {
                result.HasRemainingLocks = true;
                result.RemainingLocks = remainingLocks;
                result.RemainingLocksReturnQty = remainingLocks.Sum(x => x.AssignQuantity - x.PickedQty);
            }
            // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–库存货物
            var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.StockId == stockId &&
                            (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
                             it.Status == StockStatusEmun.入库完成.ObjToInt() ||
                             it.Status == StockStatusEmun.出库锁定.ObjToInt()))
                .Where(it => it.StockQuantity > 0) // æœ‰åº“存的
                .ToListAsync();
            if (palletStockGoods.Any())
            {
                result.HasPalletStockGoods = true;
                result.PalletStockGoods = palletStockGoods;
                result.PalletStockReturnQty = palletStockGoods.Sum(x => x.StockQuantity);
            }
            //  æ£€æŸ¥æ‹†åŒ…记录
            var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                .ToListAsync();
            if (splitRecords.Any())
            {
                result.HasSplitRecords = true;
                result.SplitRecords = splitRecords;
                result.SplitReturnQty = await CalculateSplitReturnQuantity(splitRecords, stockId);
            }
            result.TotalReturnQty = result.RemainingLocksReturnQty + result.PalletStockReturnQty + result.SplitReturnQty;
            result.HasItemsToReturn = result.TotalReturnQty > 0;
            return result;
        }
        private async Task<decimal> CalculateSplitReturnQuantity(List<Dt_SplitPackageRecord> splitRecords, int stockId)
        {
            decimal totalQty = 0;
            var processedBarcodes = new HashSet<string>();
            foreach (var splitRecord in splitRecords)
            {
                // æ£€æŸ¥åŽŸæ¡ç 
                if (!processedBarcodes.Contains(splitRecord.OriginalBarcode))
                {
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockId)
                        .FirstAsync();
                    if (originalStock != null && originalStock.StockQuantity > 0)
                    {
                        totalQty += originalStock.StockQuantity;
                        processedBarcodes.Add(splitRecord.OriginalBarcode);
                    }
                }
                // æ£€æŸ¥æ–°æ¡ç 
                if (!processedBarcodes.Contains(splitRecord.NewBarcode))
                {
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockId)
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        totalQty += newStock.StockQuantity;
                        processedBarcodes.Add(splitRecord.NewBarcode);
                    }
                }
            }
            return totalQty;
        }
        private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode)
        {
            // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
            var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
                .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
            if (allPicked)
            {
                return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
            }
            else
            {
                return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
            }
        }
        private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo,
            Dt_Task task, ReturnAnalysisResult analysis)
        {
            // æƒ…况1:处理未分拣的出库锁定记录
            if (analysis.HasRemainingLocks)
            {
                await HandleRemainingLocksReturn(analysis.RemainingLocks, stockInfo.Id);
                // å…³é”®ï¼šæ›´æ–°è®¢å•明细的已拣选数量
                await UpdateOrderDetailsOnReturn(analysis.RemainingLocks);
            }
            // æƒ…况2:处理托盘上其他库存货物
            if (analysis.HasPalletStockGoods)
            {
                await HandlePalletStockGoodsReturn(analysis.PalletStockGoods);
            }
            // æƒ…况3:处理拆包记录
            if (analysis.HasSplitRecords)
            {
                await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode);
            }
            // æ›´æ–°åº“存主表状态
            await UpdateStockInfoStatus(stockInfo);
        }
        private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks, int stockId)
        {
            var lockIds = remainingLocks.Select(x => x.Id).ToList();
            // æ›´æ–°å‡ºåº“锁定记录状态为回库中
            await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                .SetColumns(it => new Dt_OutStockLockInfo
                {
                    Status = (int)OutLockStockStatusEnum.回库中
                })
                .Where(it => lockIds.Contains(it.Id))
                .ExecuteCommandAsync();
            // å¤„理库存记录
            foreach (var lockInfo in remainingLocks)
            {
                decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                // æŸ¥æ‰¾å¯¹åº”的库存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                    .FirstAsync();
                if (stockDetail != null)
                {
                    // æ¢å¤åº“存状态
                    stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
                    stockDetail.Status = StockStatusEmun.入库完成.ObjToInt();
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                }
                else
                {
                    // åˆ›å»ºæ–°çš„库存记录(拆包产生的新条码)
                    var newStockDetail = new Dt_StockInfoDetail
                    {
                        StockId = lockInfo.StockId,
                        MaterielCode = lockInfo.MaterielCode,
                        MaterielName = lockInfo.MaterielName,
                        OrderNo = lockInfo.OrderNo,
                        BatchNo = lockInfo.BatchNo,
                        StockQuantity = returnQty,
                        OutboundQuantity = 0,
                        Barcode = lockInfo.CurrentBarcode,
                        InboundOrderRowNo = "",
                        Status = StockStatusEmun.入库完成.ObjToInt(),
                        SupplyCode = lockInfo.SupplyCode,
                        WarehouseCode = lockInfo.WarehouseCode,
                        Unit = lockInfo.Unit
                    };
                    await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                }
            }
        }
        private async Task UpdateOrderDetailsOnReturn(List<Dt_OutStockLockInfo> remainingLocks)
        {
            // æŒ‰è®¢å•明细分组
            var orderDetailGroups = remainingLocks.GroupBy(x => x.OrderDetailId);
            foreach (var group in orderDetailGroups)
            {
                var orderDetailId = group.Key;
                var totalReturnQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
                // èŽ·å–å½“å‰è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == orderDetailId);
                if (orderDetail != null)
                {
                    // è°ƒæ•´å·²æ‹£é€‰æ•°é‡å’Œå·²å‡ºåº“数量
                    decimal newPickedQty = Math.Max(0, orderDetail.PickedQty - totalReturnQty);
                    decimal newOverOutQuantity = Math.Max(0, orderDetail.OverOutQuantity - totalReturnQty);
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                        .SetColumns(it => new Dt_OutboundOrderDetail
                        {
                            PickedQty = newPickedQty,
                            OverOutQuantity = newOverOutQuantity
                        })
                        .Where(it => it.Id == orderDetailId)
                        .ExecuteCommandAsync();
                }
            }
        }
        private async Task HandlePalletStockGoodsReturn(List<Dt_StockInfoDetail> palletStockGoods)
        {
            foreach (var stockGood in palletStockGoods)
            {
                // æ¢å¤åº“存状态
                stockGood.OutboundQuantity = 0;
                stockGood.Status = StockStatusEmun.入库完成.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
            }
        }
        private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, string orderNo, string palletCode)
        {
            // æ›´æ–°æ‹†åŒ…记录状态
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已回库
                })
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && !x.IsReverted)
                .ExecuteCommandAsync();
        }
        private async Task UpdateStockInfoStatus(Dt_StockInfo stockInfo)
        {
            // æ›´æ–°åº“存主表状态
            stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
            await _stockInfoService.Db.Updateable(stockInfo).ExecuteCommandAsync();
        }
        private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, ReturnAnalysisResult analysis)
        {
            var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                .FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
            // åˆ†é…æ–°è´§ä½ï¼ˆå›žåº“到存储位)
            var newLocation = _locationInfoService.AssignLocation(firstLocation.LocationType);
            Dt_Task returnTask = new()
            {
                CurrentAddress = stations[originalTask.TargetAddress],
                Grade = 0,
                PalletCode = palletCode,
                NextAddress = "",
                OrderNo = originalTask.OrderNo,
                Roadway = newLocation.RoadwayNo,
                SourceAddress = stations[originalTask.TargetAddress],
                TargetAddress = newLocation.LocationCode,
                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                TaskType = TaskTypeEnum.InPick.ObjToInt(),
                PalletType = originalTask.PalletType,
                WarehouseId = originalTask.WarehouseId,
            };
            // ä¿å­˜å›žåº“任务
            await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
            // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
              _taskRepository.Db.Deleteable(originalTask);
            // ç»™ ESS å‘送流动信号和创建任务
            await SendESSCommands(palletCode, originalTask, returnTask);
        }
        private async Task<int> GenerateTaskNumber()
        {
            return await _dailySequenceService.GetNextSequenceAsync();
        }
        private async Task SendESSCommands(string palletCode, Dt_Task originalTask, Dt_Task returnTask)
        {
            try
            {
                // 1. å‘送流动信号
                var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                {
                    slotCode = movestations[originalTask.TargetAddress],
                    containerCode = palletCode
                });
                if (moveResult)
                {
                    // 2. åˆ›å»ºå›žåº“任务
                    var essTask = new TaskModel()
                    {
                        taskType = "putaway",
                        taskGroupCode = "",
                        groupPriority = 0,
                        tasks = new List<TasksType>
                {
                    new()
                    {
                        taskCode = returnTask.TaskNum.ToString(),
                        taskPriority = 0,
                        taskDescribe = new TaskDescribeType
                        {
                            containerCode = palletCode,
                            containerType = "CT_KUBOT_STANDARD",
                            fromLocationCode = stations.GetValueOrDefault(originalTask.TargetAddress) ?? "",
                            toStationCode = "",
                            toLocationCode = returnTask.TargetAddress,
                            deadline = 0,
                            storageTag = ""
                        }
                    }
                }
                    };
                    var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                    _logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"ReturnRemaining ESS命令发送失败: {ex.Message}");
                throw new Exception($"ESS系统通信失败: {ex.Message}");
            }
        }
        #endregion
        /// <summary>
        /// å›žåº“操作
        /// </summary>
        //public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        //{
        //    try
        //    {
        //        _unitOfWorkManage.BeginTran();
        //        // èŽ·å–æ‰€æœ‰æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•ï¼ŒåŒ…æ‹¬æ‹†åŒ…äº§ç”Ÿçš„è®°å½•
        //        var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
        //            .Where(it => it.OrderNo == orderNo && it.Status == (int)OutLockStockStatusEnum.出库中)
        //            .ToListAsync();
        //        var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
        //        var tasks = new List<Dt_Task>();
        //        // æŸ¥è¯¢ä»»åŠ¡è¡¨
        //        var task = remainingLocks.Any()
        //            ? _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault()
        //            : _taskRepository.QueryData(x => x.PalletCode == palletCode).FirstOrDefault();
        //        if (task == null)
        //        {
        //            return WebResponseContent.Instance.Error("未找到对应的任务信息");
        //        }
        //        // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–非出库货物(库存货物)
        //        var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //            .Where(it => it.StockId == stockinfo.Id &&
        //                        (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
        //                         it.Status == StockStatusEmun.入库完成.ObjToInt() ||
        //                         it.Status == StockStatusEmun.出库锁定.ObjToInt()))
        //            .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // æœªå®Œå…¨å‡ºåº“çš„
        //            .ToListAsync();
        //        // æ£€æŸ¥æ‹†åŒ…记录,找出需要回库的条码
        //        var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
        //            .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
        //            .ToListAsync();
        //        // è®¡ç®—需要回库的拆包条码
        //        var splitBarcodesToReturn = new List<string>();
        //        foreach (var splitRecord in splitRecords)
        //        {
        //            // æ£€æŸ¥åŽŸæ¡ç æ˜¯å¦è¿˜æœ‰åº“å­˜éœ€è¦å›žåº“
        //            var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //                .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockinfo.Id)
        //                .FirstAsync();
        //            if (originalStock != null && originalStock.StockQuantity > 0)
        //            {
        //                splitBarcodesToReturn.Add(splitRecord.OriginalBarcode);
        //            }
        //            // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦è¿˜æœ‰åº“存需要回库
        //            var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //                .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockinfo.Id)
        //                .FirstAsync();
        //            if (newStock != null && newStock.StockQuantity > 0)
        //            {
        //                splitBarcodesToReturn.Add(splitRecord.NewBarcode);
        //            }
        //        }
        //        // å¦‚果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物,也无拆包剩余货物)
        //        if (!remainingLocks.Any() && !palletStockGoods.Any() && !splitBarcodesToReturn.Any())
        //        {
        //            // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
        //            var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
        //                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
        //                .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
        //            if (allPicked)
        //            {
        //                return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
        //            }
        //            else
        //            {
        //                return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
        //            }
        //        }
        //        var firstlocation = _locationInfoService.Db.Queryable<Dt_LocationInfo>().First(x => x.LocationCode == task.SourceAddress);
        //        decimal totalReturnQty = 0;
        //        // æƒ…况1:处理未分拣的出库锁定记录
        //        if (remainingLocks.Any(x => x.PalletCode == palletCode))
        //        {
        //            var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
        //            totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
        //            if (totalReturnQty > 0)
        //            {
        //                // åˆ†é…æ–°è´§ä½
        //                var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
        //                // æ›´æ–°å‡ºåº“锁定记录状态
        //                var lockIds = palletLocks.Select(x => x.Id).ToList();
        //                await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
        //                    .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.回库中 })
        //                    .Where(it => lockIds.Contains(it.Id))
        //                    .ExecuteCommandAsync();
        //                // å¤„理库存记录
        //                foreach (var lockInfo in palletLocks)
        //                {
        //                    decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
        //                    // æ£€æŸ¥åº“存记录是否存在
        //                    var existingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //                        .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
        //                        .FirstAsync();
        //                    if (existingStock != null)
        //                    {
        //                        // åº“存记录存在,恢复锁定数量
        //                        existingStock.OutboundQuantity = 0;
        //                        await _stockInfoDetailService.Db.Updateable(existingStock).ExecuteCommandAsync();
        //                    }
        //                    else
        //                    {
        //                        // åº“存记录不存在(可能是拆包产生的新条码),创建新的库存记录
        //                        var newStockDetail = new Dt_StockInfoDetail
        //                        {
        //                            StockId = lockInfo.StockId,
        //                            MaterielCode = lockInfo.MaterielCode,
        //                            MaterielName = lockInfo.MaterielName,
        //                            OrderNo = lockInfo.OrderNo,
        //                            BatchNo = lockInfo.BatchNo,
        //                            StockQuantity = returnQty,
        //                            OutboundQuantity = 0,
        //                            Barcode = lockInfo.CurrentBarcode,
        //                            InboundOrderRowNo = "",
        //                            Status = StockStatusEmun.入库完成.ObjToInt(),
        //                            SupplyCode = lockInfo.SupplyCode,
        //                            WarehouseCode = lockInfo.WarehouseCode,
        //                            Unit = lockInfo.Unit
        //                        };
        //                        await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
        //                    }
        //                }
        //                // åˆ›å»ºå›žåº“任务
        //                CreateReturnTask(tasks, task, palletCode, newLocation);
        //            }
        //        }
        //        // æƒ…况2:处理拆包剩余的库存货物
        //        if (splitBarcodesToReturn.Any())
        //        {
        //            decimal splitReturnQty = 0;
        //            foreach (var barcode in splitBarcodesToReturn)
        //            {
        //                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //                    .Where(it => it.Barcode == barcode && it.StockId == stockinfo.Id)
        //                    .FirstAsync();
        //                if (stockDetail != null && stockDetail.StockQuantity > 0)
        //                {
        //                    splitReturnQty += stockDetail.StockQuantity;
        //                    // æ¢å¤åº“存状态为入库完成
        //                    stockDetail.OutboundQuantity = 0;
        //                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
        //                }
        //            }
        //            totalReturnQty += splitReturnQty;
        //            // å¦‚果没有创建任务,创建回库任务
        //            if (!tasks.Any())
        //            {
        //                var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
        //                CreateReturnTask(tasks, task, palletCode, newLocation);
        //            }
        //        }
        //        // æƒ…况3:出库货物已分拣完,但托盘上还有其他库存货物需要回库
        //        if (palletStockGoods.Any() && !remainingLocks.Any(x => x.PalletCode == palletCode))
        //        {
        //            decimal otherReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
        //            totalReturnQty += otherReturnQty;
        //            // æ›´æ–°è¿™äº›åº“存货物的状态
        //            foreach (var stockGood in palletStockGoods)
        //            {
        //                stockGood.OutboundQuantity = 0;
        //                await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
        //            }
        //            // å¦‚果没有创建任务,创建回库任务
        //            if (!tasks.Any())
        //            {
        //                var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
        //                CreateReturnTask(tasks, task, palletCode, newLocation);
        //            }
        //        }
        //        var allSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
        //            .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
        //            .ToListAsync();
        //        foreach (var record in allSplitRecords)
        //        {
        //            record.Status = (int)SplitPackageStatusEnum.已回库;
        //            await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
        //        }
        //        // ä¿å­˜ä»»åŠ¡ ç»™ESS下发任务
        //        if (tasks.Any())
        //        {
        //            try
        //            {
        //                await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
        //                var targetAddress = task.TargetAddress;
        //                _taskRepository.DeleteData(task);
        //                // ç»™ ESS æµåŠ¨ä¿¡å·å’Œåˆ›å»ºä»»åŠ¡
        //                try
        //                {
        //                    var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
        //                    {
        //                        slotCode = movestations[targetAddress],
        //                        containerCode = palletCode
        //                    });
        //                    if (result)
        //                    {
        //                        TaskModel esstask = new TaskModel()
        //                        {
        //                            taskType = "putaway",
        //                            taskGroupCode = "",
        //                            groupPriority = 0,
        //                            tasks = new List<TasksType>
        //                    {
        //                        new()
        //                        {
        //                            taskCode = tasks.First().TaskNum.ToString(),
        //                            taskPriority = 0,
        //                            taskDescribe = new TaskDescribeType {
        //                                containerCode = palletCode,
        //                                containerType = "CT_KUBOT_STANDARD",
        //                                fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
        //                                toStationCode = "",
        //                                toLocationCode = tasks.First().TargetAddress,
        //                                deadline = 0, storageTag = ""
        //                            }
        //                        }
        //                    }
        //                        };
        //                        var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
        //                        _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž:  " + resulttask);
        //                    }
        //                }
        //                catch (Exception ex)
        //                {
        //                    _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž catch err:  " + ex.Message);
        //                }
        //                _unitOfWorkManage.CommitTran();
        //                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
        //            }
        //            catch (Exception ex)
        //            {
        //                _unitOfWorkManage.RollbackTran();
        //                return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
        //            }
        //        }
        //        _unitOfWorkManage.RollbackTran();
        //        return WebResponseContent.Instance.Error("未创建任何回库任务");
        //    }
        //    catch (Exception ex)
        //    {
        //        _unitOfWorkManage.RollbackTran();
        //        return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
        //    }
        //}
        /// <summary>
@@ -802,62 +1512,7 @@
            return outboundFinished && hasRemainingGoods;
        }
        // å–消拣选功能
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                //查找拣选记录
                var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.PalletCode == palletCode &&
                               it.Barcode == barcode)
                    .OrderByDescending(it => it.PickTime)
                    .FirstAsync();
                if (pickingRecord == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选记录");
                // æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.Id == pickingRecord.OutStockLockId)
                    .FirstAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的出库锁定信息");
                //检查是否可以取消(状态必须是拣选完成)
                if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                    return WebResponseContent.Instance.Error("当前状态不允许取消分拣");
                decimal cancelQty = pickingRecord.PickQuantity;
                // æ£€æŸ¥æ‹†åŒ…链关系
                var splitChain = await GetSplitChain(barcode);
                if (splitChain.Any())
                {
                    // æƒ…况A:处理拆包链的取消(多次手动拆包)
                    await HandleSplitChainCancel(orderNo, palletCode, barcode, cancelQty, lockInfo, pickingRecord, splitChain);
                }
                else
                {
                    // æƒ…况B:处理普通条码的取消
                    await HandleNormalBarcodeCancel(orderNo, palletCode, barcode, cancelQty, lockInfo, pickingRecord);
                }
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{cancelQty}");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        /// <summary>
        /// èŽ·å–æ‹†åŒ…é“¾ï¼ˆä»Žå½“å‰æ¡ç è¿½æº¯åˆ°åŽŸå§‹æ¡ç ï¼‰
        /// </summary>
@@ -1076,38 +1731,9 @@
            }
        }
        /// <summary>
        /// è®°å½•取消操作历史
        /// </summary>
        private async Task RecordCancelHistory(string orderNo, string palletCode, string barcode,
            decimal cancelQty, int pickingRecordId, string materielCode, string reason)
        {
            //var cancelHistory = new Dt_PickingCancelRecord
            //{
            //    OrderNo = orderNo,
            //    PalletCode = palletCode,
            //    Barcode = barcode,
            //    CancelQuantity = cancelQty,
            //    CancelTime = DateTime.Now,
            //    Operator = App.User.UserName,
            //    OriginalPickingRecordId = pickingRecordId,
            //    MaterielCode = materielCode,
            //    Reason = reason
            //};
            //await Db.Insertable(cancelHistory).ExecuteCommandAsync();
        }
        /// <summary>
        /// æ‹†åŒ…链项
        /// </summary>
        public class SplitChainItem
        {
            public Dt_SplitPackageRecord SplitRecord { get; set; }
            public string OriginalBarcode { get; set; }
            public string NewBarcode { get; set; }
            public decimal SplitQuantity { get; set; }
        }
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
        {
@@ -1351,4 +1977,33 @@
        }
    }
    #region è¿”回
    /// <summary>
    /// æ‹†åŒ…链项
    /// </summary>
    public class SplitChainItem
    {
        public Dt_SplitPackageRecord SplitRecord { get; set; }
        public string OriginalBarcode { get; set; }
        public string NewBarcode { get; set; }
        public decimal SplitQuantity { get; set; }
    }
    public  class ReturnAnalysisResult
    {
        public bool HasItemsToReturn { get; set; }
        public bool HasRemainingLocks { get; set; }
        public bool HasPalletStockGoods { get; set; }
        public bool HasSplitRecords { get; set; }
        public decimal RemainingLocksReturnQty { get; set; }
        public decimal PalletStockReturnQty { get; set; }
        public decimal SplitReturnQty { get; set; }
        public decimal TotalReturnQty { get; set; }
        public List<Dt_OutStockLockInfo> RemainingLocks { get; set; } = new List<Dt_OutStockLockInfo>();
        public List<Dt_StockInfoDetail> PalletStockGoods { get; set; } = new List<Dt_StockInfoDetail>();
        public List<Dt_SplitPackageRecord> SplitRecords { get; set; } = new List<Dt_SplitPackageRecord>();
    }
    #endregion
}