pan
2025-11-21 4a890499facf5b702b37966429d68d2bbdf363f0
提交
已修改3个文件
2314 ■■■■■ 文件已修改
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 2243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs
@@ -15,17 +15,14 @@
    {
        IRepository<Dt_PickingRecord> Repository { get; }
        Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode);
        Task<bool> CheckPalletNeedReturn(string orderNo, string palletCode);
        Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> DirectOutbound(DirectOutboundRequest request);
        Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo);
        Task<WebResponseContent> GetPalletOutboundStatus(string palletCode);
        Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode);
        Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId);
        Task<object> GetPickingSummary(ConfirmPickingDto dto);
        Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode);
        Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode);
        Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason);
        Task<WebResponseContent> ValidateBarcode(string barcode);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -85,171 +85,69 @@
            _dailySequenceService = dailySequenceService;
        }
        #region æŸ¥è¯¢å‡ºåº“详情列表
        public async Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo)
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
        {
            var locks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(t => t.OrderNo == orderNo)
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 1)
                .ToListAsync();
            return locks.Select(t => new OutStockLockListResp
            {
                Id = t.Id,
                // TaskNum = t.TaskNum,
                PalletCode = t.PalletCode,
                CurrentBarcode = t.CurrentBarcode,
                AssignQuantity = t.AssignQuantity,
                PickedQty = t.PickedQty,
                Status = t.Status,
                //  IsSplitted = t.IsSplitted
            }).ToList();
            return list.Where(x => x.RemainQuantity > 0).ToList();
        }
        #endregion
        public async Task<WebResponseContent> ValidateBarcode(string barcode)
        // èŽ·å–å·²æ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode)
        {
            try
            {
                if (string.IsNullOrEmpty(barcode))
                {
                    return WebResponseContent.Instance.Error("条码不能为空");
                }
                // æ ¹æ®æ¡ç æŸ¥è¯¢åº“存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Includes(x => x.StockInfo)
                    .Where(x => x.Barcode == barcode)
                    .FirstAsync();
                if (stockDetail == null)
                {
                    return WebResponseContent.Instance.Error("条码不存在");
                }
                var result = new
                {
                    Barcode = barcode,
                    MaterielCode = stockDetail.MaterielCode,
                    BatchNo = stockDetail.BatchNo,
                    AvailableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity,
                    LocationCode = stockDetail.StockInfo?.LocationCode,
                    PalletCode = stockDetail.StockInfo?.PalletCode
                };
                return WebResponseContent.Instance.OK(null, result);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"条码验证失败: {ex.Message}");
            }
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 6)
                .ToListAsync();
            return list;
        }
        // æ£€æŸ¥å¹¶æ›´æ–°è®¢å•状态
        private async Task CheckAndUpdateOrderStatus(string orderNo)
        // èŽ·å–æ‹£é€‰æ±‡æ€»
        public async Task<object> GetPickingSummary(ConfirmPickingDto dto)
        {
            var orderDetails = _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                      .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id) // å…³è”条件:父表 Id = å­è¡¨ OrderId
                      .Where((o, item) => item.OrderNo == orderNo) // è¿‡æ»¤çˆ¶è¡¨ OrderNo
                      .Select((o, item) => o) // åªè¿”回子表数据
                      .ToList();
            //var orderDetails = await _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
            //    .Where(x => x.OrderId == orderNo.ObjToInt())
            //    .ToListAsync();
            bool allCompleted = true;
            foreach (var detail in orderDetails)
            var picked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
             .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
             .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
             .Where(x => x.Status == 6)
             .GroupBy(x => new { x.PalletCode, x.MaterielCode })
             .Select(x => new SummaryPickingDto
             {
                 PalletCode = x.PalletCode,
                 MaterielCode = x.MaterielCode,
                 pickedCount = SqlFunc.AggregateCount(x.Id)
             }).FirstAsync();
            if (picked == null)
            {
                if (detail.OverOutQuantity < detail.NeedOutQuantity)
                {
                    allCompleted = false;
                    break;
                }
                picked = new SummaryPickingDto { pickedCount = 0 };
            }
            if (allCompleted)
            var summary = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
                .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
                .Where(x => x.Status == 1)
                .GroupBy(x => new { x.PalletCode, x.MaterielCode })
                .Select(x => new SummaryPickingDto
                {
                    PalletCode = x.PalletCode,
                    MaterielCode = x.MaterielCode,
                    UnpickedCount = SqlFunc.AggregateCount(x.Id),
                    UnpickedQuantity = SqlFunc.AggregateSum(x.AssignQuantity) - SqlFunc.AggregateSum(x.PickedQty),
                }).FirstAsync();
            if (summary == null)
            {
                try
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == 2) // å·²å®Œæˆ
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    var outboundOrder = _stockInfoService.Db.Queryable<Dt_OutboundOrder>().First(x => x.OrderNo == orderNo);
                    if (outboundOrder != null && outboundOrder.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt())
                    {
                        if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt().ObjToInt())//调拨出库
                        {
                        }
                        else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt()) //重检出库
                        {
                        }
                        else
                        {
                            var feedmodel = new FeedbackOutboundRequestModel
                            {
                                reqCode = Guid.NewGuid().ToString(),
                                reqTime = DateTime.Now.ToString(),
                                business_type = outboundOrder.BusinessType,
                                factoryArea = outboundOrder.FactoryArea,
                                operationType = 1,
                                Operator = outboundOrder.Operator,
                                orderNo = outboundOrder.UpperOrderNo,
                                status = outboundOrder.OrderStatus,
                                details = new List<FeedbackOutboundDetailsModel>()
                            };
                            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
                               {
                                   materialCode = group.Key.MaterielCode,
                                   lineNo = group.Key.lineNo,
                                   warehouseCode = group.Key.WarehouseCode,
                                   currentDeliveryQty = group.Sum(x => x.OrderQuantity),
                                   // warehouseCode= "1072",
                                   unit = group.Key.Unit,
                                   barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                                   {
                                       barcode = row.CurrentBarcode,
                                       supplyCode = row.SupplyCode,
                                       batchNo = row.BatchNo,
                                       unit = row.Unit,
                                       qty = row.PickedQty
                                   }).ToList()
                               }).ToList();
                            feedmodel.details = groupedData;
                            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)
                {
                    _logger.LogError(" OutboundPickingService  FeedbackOutbound : " + ex.Message);
                }
                summary = new SummaryPickingDto { pickedCount = 0 };
            }
            summary.pickedCount = picked.pickedCount;
            return summary;
        }
        #region æ ¸å¿ƒä¸šåŠ¡æµç¨‹
        public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
        {
@@ -257,288 +155,32 @@
            {
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯è¾“入参数
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
                {
                    throw new Exception("订单号、托盘码和条码不能为空");
                }
                // 1. å‰ç½®éªŒè¯å’Œä¸šåŠ¡æ£€æŸ¥
                var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                // 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.AssignQuantity > it.PickedQty)
                    .FirstAsync();
                var (lockInfo, orderDetail, stockDetail) = validationResult.Data;
                if (lockInfo == null)
                {
                    lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(it => it.CurrentBarcode == barcode &&
                                   it.Status == (int)OutLockStockStatusEnum.出库中 &&
                                   it.AssignQuantity > it.PickedQty)
                        .FirstAsync();
                // 2. è®¡ç®—实际拣选数量
                var quantityResult = await CalculateActualPickingQuantity(lockInfo, orderDetail, stockDetail);
                if (!quantityResult.IsValid)
                    return WebResponseContent.Instance.Error(quantityResult.ErrorMessage);
                    if (lockInfo == null)
                    {
                        var completedLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                            .Where(it => it.CurrentBarcode == barcode &&
                                       (it.Status == (int)OutLockStockStatusEnum.拣选完成 ||
                                        it.PickedQty >= it.AssignQuantity))
                            .FirstAsync();
                var (actualQty, adjustedReason) = quantityResult.Data;
                        if (completedLockInfo != null)
                            throw new Exception($"条码{barcode}已经完成分拣,不能重复分拣");
                        else
                            throw new Exception($"条码{barcode}不属于托盘{palletCode}或不存在待分拣记录");
                    }
                }
                // 3. æ‰§è¡Œåˆ†æ‹£é€»è¾‘
                var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, orderNo, palletCode, barcode, actualQty);
                if (lockInfo.PalletCode != palletCode)
                    throw new Exception($"条码{barcode}不属于托盘{palletCode}");
                // 4. æ›´æ–°ç›¸å…³æ•°æ®
                await UpdateOrderRelatedData(orderDetail.Id, pickingResult.ActualPickedQty, orderNo);
                // æ£€æŸ¥æ‹£é€‰åŽ†å²ï¼Œé˜²æ­¢é‡å¤åˆ†æ‹£
                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}已经分拣过,不能重复分拣");
                }
                // èŽ·å–è®¢å•æ˜Žç»†å¹¶æ£€æŸ¥æ•°é‡é™åˆ¶
                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();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                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;
                    // æ›´æ–°åŽŸæ¡ç åº“å­˜ä¸ºå‰©ä½™æ•°é‡
                    stockDetail.StockQuantity = remainingStockQty;
                    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,
                        OrderDetailId = lockInfo.OrderDetailId,
                        BatchNo = lockInfo.BatchNo,
                        MaterielCode = lockInfo.MaterielCode,
                        MaterielName = lockInfo.MaterielName,
                        StockId = lockInfo.StockId,
                        OrderQuantity = actualQty,
                        OriginalQuantity = actualQty,
                        AssignQuantity = actualQty,
                        PickedQty = actualQty,
                        LocationCode = lockInfo.LocationCode,
                        PalletCode = lockInfo.PalletCode,
                        TaskNum = lockInfo.TaskNum,
                        Status = (int)OutLockStockStatusEnum.拣选完成,
                        Unit = lockInfo.Unit,
                        SupplyCode = lockInfo.SupplyCode,
                        OrderType = lockInfo.OrderType,
                        CurrentBarcode = newBarcode,
                        OriginalLockQuantity = actualQty,
                        IsSplitted = 1,
                        ParentLockId = lockInfo.Id
                    };
                    var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync();
                    newLockInfo.Id = newLockId;
                    // è®°å½•拆包历史
                    var splitHistory = new Dt_SplitPackageRecord
                    {
                        FactoryArea = lockInfo.FactoryArea,
                        TaskNum = lockInfo.TaskNum,
                        OutStockLockInfoId = lockInfo.Id,
                        StockId = stockDetail.StockId,
                        Operator = App.User.UserName,
                        IsReverted = false,
                        OriginalBarcode = barcode,
                        NewBarcode = newBarcode,
                        SplitQty = actualQty,
                        RemainQuantity = remainingStockQty,
                        MaterielCode = lockInfo.MaterielCode,
                        SplitTime = DateTime.Now,
                        OrderNo = lockInfo.OrderNo,
                        PalletCode = lockInfo.PalletCode,
                        Status = (int)SplitPackageStatusEnum.已拣选
                    };
                    await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
                    // æ›´æ–°åŽŸé”å®šä¿¡æ¯ä¸ºå‰©ä½™åº“å­˜æ•°é‡
                    lockInfo.AssignQuantity = remainingStockQty;
                    lockInfo.PickedQty = 0;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    splitResults.Add(new SplitResult
                    {
                        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"),
                    });
                    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)
                {
                    // æƒ…况2: åˆ†é…æ•°é‡ç­‰äºŽåº“存数量,整包出库
                    stockDetail.StockQuantity = 0;
                    stockDetail.OutboundQuantity = 0;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    lockInfo.PickedQty += actualQty;
                    lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                }
                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();
                    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 => 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 = finalLockInfo.FactoryArea,
                    TaskNo = task?.TaskNum ?? 0,
                    LocationCode = task?.SourceAddress ?? "",
                    StockId = finalStockId,
                    OrderNo = orderNo,
                    OrderDetailId = finalLockInfo.OrderDetailId,
                    PalletCode = palletCode,
                    Barcode = finalBarcode,
                    MaterielCode = finalLockInfo.MaterielCode,
                    PickQuantity = actualPickedQty,
                    PickTime = DateTime.Now,
                    Operator = App.User.UserName,
                    OutStockLockId = finalLockInfo.Id
                };
                await Db.Insertable(pickingHistory).ExecuteCommandAsync();
                // 5. è®°å½•操作历史
                await RecordPickingHistory(pickingResult, orderNo, palletCode);
                _unitOfWorkManage.CommitTran();
                if (splitResults.Any())
                {
                    return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = splitResults });
                }
                return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
                return CreatePickingResponse(pickingResult, adjustedReason);
            }
            catch (Exception ex)
            {
@@ -554,77 +196,19 @@
            {
                _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();
                // 1. å‰ç½®éªŒè¯
                var validationResult = await ValidateCancelRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                if (pickingRecord == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选记录");
                var (pickingRecord, lockInfo, orderDetail) = validationResult.Data;
                // æŸ¥æ‰¾å‡ºåº“锁定信息
                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);
                // 2. æ‰§è¡Œå–消逻辑
                await ExecuteCancelLogic(lockInfo, pickingRecord, orderDetail, orderNo);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{cancelQty}");
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{pickingRecord.PickQuantity}");
            }
            catch (Exception ex)
            {
@@ -632,6 +216,438 @@
                _logger.LogError($"CancelPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. åŸºç¡€éªŒè¯
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
                    return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
                // 2. èŽ·å–åº“å­˜å’Œä»»åŠ¡ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .FirstAsync(x => x.PalletCode == palletCode);
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} å¯¹åº”的库存信息");
                var task = await GetCurrentTask(orderNo, palletCode);
                if (task == null)
                    return WebResponseContent.Instance.Error("未找到对应的任务信息");
                // 3. åˆ†æžéœ€è¦å›žåº“的货物
                var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id);
                if (!returnAnalysis.HasItemsToReturn)
                    return await HandleNoReturnItems(orderNo, palletCode);
                // 4. æ‰§è¡Œå›žåº“操作
                await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, returnAnalysis);
                // 5. åˆ›å»ºå›žåº“任务
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, returnAnalysis);
                _unitOfWorkManage.CommitTran();
                // 6. æ›´æ–°è®¢å•状态(不触发MES回传)
                await UpdateOrderStatusForReturn(orderNo);
                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{returnAnalysis.TotalReturnQty}");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"ReturnRemaining失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
            }
        }
        #endregion
        #region åˆ†æ‹£ç¡®è®¤ç§æœ‰æ–¹æ³•
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>> ValidatePickingRequest(string orderNo, string palletCode, string barcode)
        {
            // 1. åŸºç¡€å‚数验证
            if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("订单号、托盘码和条码不能为空");
            // 2. æŸ¥æ‰¾æœ‰æ•ˆçš„锁定信息
            var lockInfo = await FindValidLockInfo(orderNo, palletCode, barcode);
            if (lockInfo == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到有效的锁定信息");
            // 3. æ£€æŸ¥è®¢å•状态
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
                .FirstAsync();
            if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单{orderNo}已完成,不能继续分拣");
            // 4. èŽ·å–è®¢å•æ˜Žç»†
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
            if (orderDetail == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到订单明细");
            // 5. æ£€æŸ¥è®¢å•明细数量
            if (orderDetail.OverOutQuantity >= orderDetail.NeedOutQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单明细需求数量已满足");
            // 6. èŽ·å–åº“å­˜æ˜Žç»†
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
                .FirstAsync();
            if (stockDetail == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"无效的条码或物料编码");
            // 7. æ£€æŸ¥åº“存状态和数量
            if (stockDetail.StockQuantity <= 0)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}库存不足");
            if (stockDetail.Status != StockStatusEmun.出库锁定.ObjToInt())
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}状态不正确,无法分拣");
            // 8. æ£€æŸ¥æ˜¯å¦é‡å¤åˆ†æ‹£
            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)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}已经分拣过");
            return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Success((lockInfo, orderDetail, stockDetail));
        }
        private async Task<Dt_OutStockLockInfo> FindValidLockInfo(string orderNo, string palletCode, string barcode)
        {
            // ä¼˜å…ˆæŸ¥æ‰¾ç²¾ç¡®åŒ¹é…çš„记录
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo &&
                           it.Status == (int)OutLockStockStatusEnum.出库中 &&
                           it.PalletCode == palletCode &&
                           it.CurrentBarcode == barcode &&
                           it.AssignQuantity > it.PickedQty)
                .FirstAsync();
            if (lockInfo == null)
            {
                // æŸ¥æ‰¾åŒä¸€è®¢å•下的记录
                lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.CurrentBarcode == barcode &&
                               it.Status == (int)OutLockStockStatusEnum.出库中 &&
                               it.AssignQuantity > it.PickedQty)
                    .FirstAsync();
                if (lockInfo == null)
                {
                    // æ£€æŸ¥æ˜¯å¦å·²ç»å®Œæˆåˆ†æ‹£
                    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
                        return null;
                }
            }
            return lockInfo;
        }
        private async Task<ValidationResult<(decimal, string)>> CalculateActualPickingQuantity(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail)
        {
            decimal plannedQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
            decimal remainingOrderQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
            decimal stockQuantity = stockDetail.StockQuantity;
            // ä¸‰é‡æ£€æŸ¥ï¼šå–最小值
            decimal actualQty = plannedQty;
            string adjustedReason = null;
            // æ£€æŸ¥1:订单数量限制
            if (plannedQty > remainingOrderQty && remainingOrderQty > 0)
            {
                actualQty = remainingOrderQty;
                adjustedReason = $"订单数量限制:从{plannedQty}调整为{actualQty}";
            }
            // æ£€æŸ¥2:库存数量限制
            if (actualQty > stockQuantity)
            {
                actualQty = stockQuantity;
                adjustedReason = adjustedReason != null
                    ? $"{adjustedReason},库存数量限制:进一步调整为{actualQty}"
                    : $"库存数量限制:从{plannedQty}调整为{actualQty}";
            }
            // æ£€æŸ¥3:实际可拣选数量必须大于0
            if (actualQty <= 0)
            {
                return ValidationResult<(decimal, string)>.Error($"无法分拣:计划数量{plannedQty},剩余订单{remainingOrderQty},库存{stockQuantity}");
            }
            if (adjustedReason != null)
            {
                _logger.LogWarning($"分拣数量调整:{adjustedReason},订单{orderDetail.NeedOutQuantity},已出库{orderDetail.OverOutQuantity},库存{stockQuantity}");
            }
            return ValidationResult<(decimal, string)>.Success((actualQty, adjustedReason));
        }
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail,
            string orderNo, string palletCode, string barcode, decimal actualQty)
        {
            decimal stockQuantity = stockDetail.StockQuantity;
            var result = new PickingResult
            {
                FinalLockInfo = lockInfo,
                FinalBarcode = barcode,
                FinalStockId = stockDetail.Id,
                ActualPickedQty = actualQty
            };
            if (actualQty < stockQuantity)
            {
                await HandleSplitPacking(lockInfo, stockDetail, actualQty, stockQuantity, result);
            }
            else if (actualQty == stockQuantity)
            {
                await HandleFullPicking(lockInfo, stockDetail, actualQty, result);
            }
            else
            {
                await HandlePartialPicking(lockInfo, stockDetail, actualQty, stockQuantity, result);
            }
            return result;
        }
        private async Task HandleSplitPacking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal actualQty, decimal stockQuantity, PickingResult result)
        {
            decimal remainingStockQty = stockQuantity - actualQty;
            // 1. æ›´æ–°åŽŸæ¡ç åº“å­˜
            stockDetail.StockQuantity = remainingStockQty;
            stockDetail.OutboundQuantity = remainingStockQty;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
            // 3. åˆ›å»ºæ–°é”å®šä¿¡æ¯
            var newLockInfo = await CreateSplitLockInfo(lockInfo, actualQty, newBarcode);
            // 4. è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, actualQty, remainingStockQty, newBarcode);
            // 5. æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity = remainingStockQty;
            lockInfo.PickedQty = 0;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 6. è®¾ç½®ç»“æžœ
            result.FinalLockInfo = newLockInfo;
            result.FinalBarcode = newBarcode;
            result.SplitResults.AddRange(CreateSplitResults(lockInfo, actualQty, remainingStockQty, newBarcode, stockDetail.Barcode));
        }
        private async Task HandleFullPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal actualQty, PickingResult result)
        {
            // 1. æ›´æ–°åº“å­˜
            stockDetail.StockQuantity = 0;
            stockDetail.OutboundQuantity = 0;
            stockDetail.Status = StockStatusEmun.出库完成.ObjToInt();
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += actualQty;
            lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
        }
        private async Task HandlePartialPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal actualQty, decimal stockQuantity, PickingResult result)
        {
            decimal stockOutQty = stockQuantity;
            decimal remainingAssignQty = actualQty - stockQuantity;
            // 1. æ›´æ–°åº“å­˜
            stockDetail.StockQuantity = 0;
            stockDetail.OutboundQuantity = 0;
            stockDetail.Status = StockStatusEmun.出库完成.ObjToInt();
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += stockOutQty;
            lockInfo.AssignQuantity = remainingAssignQty;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 3. æ›´æ–°æ‹†åŒ…记录状态
            await UpdateSplitRecordsStatus(stockDetail.Barcode);
            result.ActualPickedQty = stockOutQty;
        }
        private async Task UpdateOrderRelatedData(int orderDetailId, decimal pickedQty, string orderNo)
        {
            // èŽ·å–æœ€æ–°çš„è®¢å•æ˜Žç»†æ•°æ®
            var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity + pickedQty;
            decimal newPickedQty = currentOrderDetail.PickedQty + pickedQty;
            // æœ€ç»ˆæ£€æŸ¥ï¼šç¡®ä¿ä¸ä¼šè¶…过订单需求数量
            if (newOverOutQuantity > currentOrderDetail.NeedOutQuantity)
            {
                throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({currentOrderDetail.NeedOutQuantity})");
            }
            // æ›´æ–°è®¢å•明细
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => new Dt_OutboundOrderDetail
                {
                    PickedQty = newPickedQty,
                    OverOutQuantity = newOverOutQuantity,
                })
                .Where(it => it.Id == orderDetailId)
                .ExecuteCommandAsync();
            // æ£€æŸ¥å¹¶æ›´æ–°è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
        }
        private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode)
        {
            var task = await _taskRepository.Db.Queryable<Dt_Task>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .FirstAsync();
            if (result.FinalLockInfo.Id <= 0)
            {
                throw new Exception($"锁定信息ID无效: {result.FinalLockInfo.Id},无法记录拣选历史");
            }
            var pickingHistory = new Dt_PickingRecord
            {
                FactoryArea = result.FinalLockInfo.FactoryArea,
                TaskNo = task?.TaskNum ?? 0,
                LocationCode = task?.SourceAddress ?? "",
                StockId = result.FinalStockId,
                OrderNo = orderNo,
                OrderDetailId = result.FinalLockInfo.OrderDetailId,
                PalletCode = palletCode,
                Barcode = result.FinalBarcode,
                MaterielCode = result.FinalLockInfo.MaterielCode,
                PickQuantity = result.ActualPickedQty,
                PickTime = DateTime.Now,
                Operator = App.User.UserName,
                OutStockLockId = result.FinalLockInfo.Id
            };
            await Db.Insertable(pickingHistory).ExecuteCommandAsync();
        }
        #endregion
        #region å–消分拣私有方法
        private async Task<ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>> ValidateCancelRequest(string orderNo, string palletCode, string barcode)
        {
            // åŸºç¡€å‚数验证
            if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单号、托盘码和条码不能为空");
            // æŸ¥æ‰¾æ‹£é€‰è®°å½•
            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 ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的拣选记录");
            // æŸ¥æ‰¾é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.Id == pickingRecord.OutStockLockId)
                .FirstAsync();
            if (lockInfo == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的出库锁定信息");
            // æ£€æŸ¥çŠ¶æ€æ˜¯å¦å…è®¸å–æ¶ˆ
            if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("当前状态不允许取消分拣");
            // æ£€æŸ¥è®¢å•状态
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
                .FirstAsync();
            if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单已出库完成,不允许取消分拣");
            // èŽ·å–è®¢å•æ˜Žç»†
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
            if (orderDetail == null)
                return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"未找到订单明细,ID: {pickingRecord.OrderDetailId}");
            return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Success((pickingRecord, lockInfo, orderDetail));
        }
        private async Task ExecuteCancelLogic(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord,
            Dt_OutboundOrderDetail orderDetail, string orderNo)
        {
            decimal cancelQty = pickingRecord.PickQuantity;
            // 1. æ£€æŸ¥å–消后数量不会为负数
            decimal newOverOutQuantity = orderDetail.OverOutQuantity - cancelQty;
            decimal newPickedQty = orderDetail.PickedQty - cancelQty;
            if (newOverOutQuantity < 0 || newPickedQty < 0)
            {
                throw new Exception($"取消分拣将导致数据异常:已出库{newOverOutQuantity},已拣选{newPickedQty}");
            }
            // 2. å¤„理不同类型的取消
            if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
            {
                await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
            }
            else
            {
                await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty);
            }
            // 3. æ›´æ–°è®¢å•明细
            await UpdateOrderDetailOnCancel(pickingRecord.OrderDetailId, cancelQty);
            // 4. åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>()
                .Where(x => x.Id == pickingRecord.Id)
                .ExecuteCommandAsync();
            // 5. é‡æ–°æ£€æŸ¥è®¢å•状态
            await UpdateOrderStatusForReturn(orderNo);
        }
        private async Task HandleSplitBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
@@ -642,9 +658,7 @@
                .FirstAsync();
            if (parentLockInfo == null)
            {
                throw new Exception("未找到父锁定信息,无法取消拆包分拣");
            }
            // æ¢å¤çˆ¶é”å®šä¿¡æ¯çš„分配数量
            parentLockInfo.AssignQuantity += cancelQty;
@@ -658,7 +672,8 @@
            if (stockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
                stockDetail.OutboundQuantity = stockDetail.StockQuantity;
                stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
@@ -667,7 +682,7 @@
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已撤销,
                    IsReverted = true
                    IsReverted = true,
                })
                .Where(x => x.NewBarcode == lockInfo.CurrentBarcode && !x.IsReverted)
                .ExecuteCommandAsync();
@@ -695,114 +710,46 @@
            if (stockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
                stockDetail.OutboundQuantity = stockDetail.StockQuantity;
                stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
        }
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        private async Task UpdateOrderDetailOnCancel(int orderDetailId, decimal cancelQty)
        {
            try
            // èŽ·å–æœ€æ–°çš„è®¢å•æ˜Žç»†æ•°æ®
            var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity - cancelQty;
            decimal newPickedQty = currentOrderDetail.PickedQty - cancelQty;
            // æ£€æŸ¥å–消后数量不会为负数
            if (newOverOutQuantity < 0 || newPickedQty < 0)
            {
                _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);
                if (stockInfo == null)
                {
                    return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} å¯¹åº”的库存信息");
                }
                var task = await GetCurrentTask(orderNo, palletCode);
                if (task == null)
                {
                    return WebResponseContent.Instance.Error("未找到对应的任务信息");
                }
                // åˆ†æžéœ€è¦å›žåº“的货物
                var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id);
                if (!returnAnalysis.HasItemsToReturn)
                {
                    return await HandleNoReturnItems(orderNo, palletCode);
                }
                // æ‰§è¡Œå›žåº“操作
                await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, returnAnalysis);
                //创建回库任务并处理ESS
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, returnAnalysis);
                _unitOfWorkManage.CommitTran();
                // æ›´æ–°è®¢å•状态
                await UpdateOrderStatusForReturn(orderNo);
                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{returnAnalysis.TotalReturnQty}");
                throw new Exception($"取消分拣将导致已出库数量({newOverOutQuantity})或已拣选数量({newPickedQty})为负数");
            }
            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)
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => new Dt_OutboundOrderDetail
                {
                    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}");
            }
                    PickedQty = newPickedQty,
                    OverOutQuantity = newOverOutQuantity,
                })
                .Where(it => it.Id == orderDetailId)
                .ExecuteCommandAsync();
        }
        #endregion
        #region Private Methods
        #region å›žåº“操作私有方法
        private async Task<Dt_StockInfo> GetStockInfo(string palletCode)
        {
            return await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .FirstAsync(x => x.PalletCode == palletCode);
        }
        private async Task<Dt_Task> GetCurrentTask(string orderNo, string palletCode)
        {
@@ -826,7 +773,7 @@
        {
            var result = new ReturnAnalysisResult();
            // èŽ·å–æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•
            // æƒ…况1:获取未分拣的出库锁定记录
            var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
@@ -840,15 +787,14 @@
                result.RemainingLocksReturnQty = remainingLocks.Sum(x => x.AssignQuantity - x.PickedQty);
            }
            // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–库存货物
            // æƒ…况2:检查托盘上是否有其他库存货物
            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) // æœ‰åº“存的
                .Where(it => it.StockQuantity > 0)
                .ToListAsync();
            if (palletStockGoods.Any())
            {
@@ -857,7 +803,7 @@
                result.PalletStockReturnQty = palletStockGoods.Sum(x => x.StockQuantity);
            }
            //  æ£€æŸ¥æ‹†åŒ…记录
            // æƒ…况3:检查拆包记录
            var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                .ToListAsync();
@@ -967,7 +913,7 @@
            await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                .SetColumns(it => new Dt_OutStockLockInfo
                {
                    Status = (int)OutLockStockStatusEnum.回库中
                    Status = (int)OutLockStockStatusEnum.回库中,
                })
                .Where(it => lockIds.Contains(it.Id))
                .ExecuteCommandAsync();
@@ -991,7 +937,7 @@
                }
                else
                {
                    // åˆ›å»ºæ–°çš„库存记录(拆包产生的新条码)
                    // åˆ›å»ºæ–°çš„库存记录
                    var newStockDetail = new Dt_StockInfoDetail
                    {
                        StockId = lockInfo.StockId,
@@ -1006,7 +952,7 @@
                        Status = StockStatusEmun.入库完成.ObjToInt(),
                        SupplyCode = lockInfo.SupplyCode,
                        WarehouseCode = lockInfo.WarehouseCode,
                        Unit = lockInfo.Unit
                        Unit = lockInfo.Unit,
                    };
                    await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                }
@@ -1037,7 +983,7 @@
                        .SetColumns(it => new Dt_OutboundOrderDetail
                        {
                            PickedQty = newPickedQty,
                            OverOutQuantity = newOverOutQuantity
                            OverOutQuantity = newOverOutQuantity,
                        })
                        .Where(it => it.Id == orderDetailId)
                        .ExecuteCommandAsync();
@@ -1051,7 +997,8 @@
            {
                // æ¢å¤åº“存状态
                stockGood.OutboundQuantity = 0;
                stockGood.Status = StockStatusEmun.入库完成.ObjToInt();
                stockGood.Status = StockStatusEmun.入库完成.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
            }
        }
@@ -1062,7 +1009,7 @@
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已回库
                    Status = (int)SplitPackageStatusEnum.已回库,
                })
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && !x.IsReverted)
                .ExecuteCommandAsync();
@@ -1071,7 +1018,7 @@
        private async Task UpdateStockInfoStatus(Dt_StockInfo stockInfo)
        {
            // æ›´æ–°åº“存主表状态
            stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
            stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
            await _stockInfoService.Db.Updateable(stockInfo).ExecuteCommandAsync();
        }
@@ -1080,7 +1027,7 @@
            var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                .FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
            // åˆ†é…æ–°è´§ä½ï¼ˆå›žåº“到存储位)
            // åˆ†é…æ–°è´§ä½
            var newLocation = _locationInfoService.AssignLocation(firstLocation.LocationType);
            Dt_Task returnTask = new()
@@ -1096,27 +1043,28 @@
                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                TaskType = TaskTypeEnum.InPick.ObjToInt(),
                PalletType = originalTask.PalletType,
                WarehouseId = originalTask.WarehouseId,
                WarehouseId = originalTask.WarehouseId
            };
            };
            // ä¿å­˜å›žåº“任务
            await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
            var targetAddress = originalTask.TargetAddress;
            // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
              _taskRepository.Db.Deleteable(originalTask);
            await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            // ç»™ ESS å‘送流动信号和创建任务
            await SendESSCommands(palletCode, originalTask, returnTask);
            await SendESSCommands(palletCode, targetAddress, returnTask);
        }
        private async Task SendESSCommands(string palletCode, Dt_Task originalTask, Dt_Task returnTask)
        private async Task SendESSCommands(string palletCode, string targetAddress, Dt_Task returnTask)
        {
            try
            {
                // 1. å‘送流动信号
                var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                {
                    slotCode = movestations[originalTask.TargetAddress],
                    slotCode = movestations[targetAddress],
                    containerCode = palletCode
                });
@@ -1129,23 +1077,23 @@
                        taskGroupCode = "",
                        groupPriority = 0,
                        tasks = new List<TasksType>
                {
                    new()
                    {
                        taskCode = returnTask.TaskNum.ToString(),
                        taskPriority = 0,
                        taskDescribe = new TaskDescribeType
                        new()
                        {
                            containerCode = palletCode,
                            containerType = "CT_KUBOT_STANDARD",
                            fromLocationCode = stations.GetValueOrDefault(originalTask.TargetAddress) ?? "",
                            toStationCode = "",
                            toLocationCode = returnTask.TargetAddress,
                            deadline = 0,
                            storageTag = ""
                            taskCode = returnTask.TaskNum.ToString(),
                            taskPriority = 0,
                            taskDescribe = new TaskDescribeType
                            {
                                containerCode = palletCode,
                                containerType = "CT_KUBOT_STANDARD",
                                fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
                                toStationCode = "",
                                toLocationCode = returnTask.TargetAddress,
                                deadline = 0,
                                storageTag = ""
                            }
                        }
                    }
                }
                    };
                    var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
@@ -1161,829 +1109,331 @@
        #endregion
        #region è®¢å•状态管理
        /// <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>
        /// åˆ›å»ºå›žåº“任务
        /// </summary>
        private void CreateReturnTask(List<Dt_Task> tasks, Dt_Task originalTask, string palletCode, Dt_LocationInfo newLocation)
        private async Task CheckAndUpdateOrderStatus(string orderNo)
        {
            Dt_Task newTask = new()
            try
            {
                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,
                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();
            };
            tasks.Add(newTask);
        }
        /// <summary>
        /// æ£€æŸ¥æ‰˜ç›˜æ˜¯å¦éœ€è¦å›žåº“的辅助方法
        /// </summary>
        public async Task<bool> CheckPalletNeedReturn(string orderNo, string palletCode)
        {
            // 1. æ£€æŸ¥æ˜¯å¦æœ‰æœªåˆ†æ‹£çš„出库记录
            var hasUnpickedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Status == 1)
                .AnyAsync();
            if (hasUnpickedLocks)
                return true;
            // 2. æ£€æŸ¥å‡ºåº“是否已完成但托盘还有库存货物
            var outboundFinished = !await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.PalletCode == palletCode && it.Status == 1)
                .AnyAsync();
            var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
            var hasRemainingGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.StockId == stockinfo.Id && it.Status == StockStatusEmun.入库确认.ObjToInt())
                .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity)
                .AnyAsync();
            return outboundFinished && hasRemainingGoods;
        }
        /// <summary>
        /// èŽ·å–æ‹†åŒ…é“¾ï¼ˆä»Žå½“å‰æ¡ç è¿½æº¯åˆ°åŽŸå§‹æ¡ç ï¼‰
        /// </summary>
        // åœ¨ GetSplitChain æ–¹æ³•中添加更严格的验证
        private async Task<List<SplitChainItem>> GetSplitChain(string currentBarcode)
        {
            var chain = new List<SplitChainItem>();
            var visited = new HashSet<string>();
            string current = currentBarcode;
            int maxDepth = 10; // é˜²æ­¢æ— é™å¾ªçޝ
            while (!string.IsNullOrEmpty(current) && maxDepth > 0)
            {
                maxDepth--;
                if (visited.Contains(current))
                bool allCompleted = true;
                foreach (var detail in orderDetails)
                {
                    _logger.LogWarning($"检测到循环引用在拆包链中: {current}");
                    break;
                    if (detail.OverOutQuantity < detail.NeedOutQuantity)
                    {
                        allCompleted = false;
                        break;
                    }
                }
                visited.Add(current);
                var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                    .FirstAsync(x => x.OrderNo == orderNo);
                var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.NewBarcode == current && !it.IsReverted)
                    .FirstAsync();
                if (outboundOrder == null) return;
                if (splitRecord == null)
                    break;
                int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                // éªŒè¯æ‹†åŒ…记录的完整性
                if (string.IsNullOrEmpty(splitRecord.OriginalBarcode))
                if (outboundOrder.OrderStatus != newStatus)
                {
                    _logger.LogError($"拆包记录 {splitRecord.Id} ç¼ºå°‘原始条码");
                    break;
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    // åªæœ‰æ­£å¸¸åˆ†æ‹£å®Œæˆæ—¶æ‰å‘MES反馈
                    if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
                    {
                        await HandleOrderCompletion(outboundOrder, orderNo);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"CheckAndUpdateOrderStatus失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
        }
        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 item = new SplitChainItem
                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)
                {
                    SplitRecord = splitRecord,
                    OriginalBarcode = splitRecord.OriginalBarcode,
                    NewBarcode = splitRecord.NewBarcode,
                    SplitQuantity = splitRecord.SplitQty
                    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}");
            }
        }
        private async Task HandleOrderCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        {
            // è°ƒæ‹¨å‡ºåº“和重检出库不需要反馈MES
            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt() ||
                outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
            {
                return;
            }
            try
            {
                var feedmodel = new FeedbackOutboundRequestModel
                {
                    reqCode = Guid.NewGuid().ToString(),
                    reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    business_type = outboundOrder.BusinessType,
                    factoryArea = outboundOrder.FactoryArea,
                    operationType = 1,
                    Operator = outboundOrder.Operator,
                    orderNo = outboundOrder.UpperOrderNo,
                    status = outboundOrder.OrderStatus,
                    details = new List<FeedbackOutboundDetailsModel>()
                };
                chain.Add(item);
                // åªèŽ·å–å·²æ‹£é€‰å®Œæˆçš„é”å®šè®°å½•
                var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成)
                    .ToListAsync();
                current = splitRecord.OriginalBarcode;
                var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                   .Select(group => new FeedbackOutboundDetailsModel
                   {
                       materialCode = group.Key.MaterielCode,
                       lineNo = group.Key.lineNo,
                       warehouseCode = group.Key.WarehouseCode,
                       currentDeliveryQty = group.Sum(x => x.PickedQty),
                       unit = group.Key.Unit,
                       barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                       {
                           barcode = row.CurrentBarcode,
                           supplyCode = row.SupplyCode,
                           batchNo = row.BatchNo,
                           unit = row.Unit,
                           qty = row.PickedQty
                       }).ToList()
                   }).ToList();
                feedmodel.details = groupedData;
                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();
                }
            }
            if (maxDepth <= 0)
            catch (Exception ex)
            {
                _logger.LogWarning($"拆包链追溯达到最大深度: {currentBarcode}");
                _logger.LogError($"FeedbackOutbound失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
            chain.Reverse();
            return chain;
        }
        /// <summary>
        /// å¤„理拆包链的取消分拣
        /// </summary>
        private async Task HandleSplitChainCancel(string orderNo, string palletCode, string barcode,
            decimal cancelQty, Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, List<SplitChainItem> splitChain)
        {
            if (!splitChain.Any())
                return;
            //  æ‰¾åˆ°åŽŸå§‹æ¡ç ï¼ˆé“¾çš„ç¬¬ä¸€ä¸ªï¼‰
            var originalSplitItem = splitChain.First();
            var originalBarcode = originalSplitItem.OriginalBarcode;
            // æŸ¥æ‰¾åŽŸå§‹æ¡ç çš„é”å®šä¿¡æ¯å’Œåº“å­˜
            var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.CurrentBarcode == originalBarcode && it.Status == (int)OutLockStockStatusEnum.出库中)
                .FirstAsync();
            var originalStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == originalBarcode && it.StockId == originalLockInfo.StockId)
                .FirstAsync();
            if (originalLockInfo == null || originalStockDetail == null)
                throw new Exception("未找到原始条码的锁定信息或库存信息");
            // æ¢å¤åŽŸå§‹æ¡ç åº“å­˜ï¼ˆå°†å–æ¶ˆçš„æ•°é‡åŠ å›žåŽ»ï¼‰
            originalStockDetail.StockQuantity += cancelQty;
            originalStockDetail.OutboundQuantity += cancelQty;
            await _stockInfoDetailService.Db.Updateable(originalStockDetail).ExecuteCommandAsync();
            // æ¢å¤åŽŸå§‹æ¡ç é”å®šä¿¡æ¯
            originalLockInfo.AssignQuantity += cancelQty;
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
            //  åˆ é™¤æ‹†åŒ…链中所有新条码的锁定信息和库存记录
            var allNewBarcodes = splitChain.Select(x => x.NewBarcode).ToList();
            // åˆ é™¤é”å®šä¿¡æ¯
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(it => allNewBarcodes.Contains(it.CurrentBarcode))
                .ExecuteCommandAsync();
            // åˆ é™¤åº“存记录(只删除拆包产生的新条码库存,保留原始条码)
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(it => allNewBarcodes.Contains(it.Barcode) && it.Barcode != originalBarcode)
                .ExecuteCommandAsync();
            // æ›´æ–°æ‹†åŒ…链中所有拆包记录状态为已拆包
            foreach (var chainItem in splitChain)
            {
                chainItem.SplitRecord.Status = (int)SplitPackageStatusEnum.已拆包;
                await _splitPackageService.Db.Updateable(chainItem.SplitRecord).ExecuteCommandAsync();
            }
            //  æ¢å¤è®¢å•明细拣选数量(使用原始锁定信息的订单明细ID)
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => it.PickedQty == it.PickedQty - cancelQty)
                .Where(it => it.Id == originalLockInfo.OrderDetailId)
                .ExecuteCommandAsync();
            //   æ¢å¤è®¢å•状态
            await CheckAndRevertOrderStatus(orderNo);
            //  åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>()
                .Where(it => it.Id == pickingRecord.Id)
                .ExecuteCommandAsync();
            ////  è®°å½•取消操作历史
            //await RecordCancelHistory(orderNo, palletCode, barcode, cancelQty, pickingRecord.Id,
            //    lockInfo.MaterielCode, "取消拆包链分拣");
        }
        /// <summary>
        /// å¤„理普通条码的取消分拣
        /// </summary>
        private async Task HandleNormalBarcodeCancel(string orderNo, string palletCode, string barcode,
            decimal cancelQty, Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord)
        #endregion
        #region è¾…助方法
        private async Task<string> GenerateNewBarcode()
        {
            // 1. æŸ¥æ‰¾åº“存信息
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == barcode && it.StockId == lockInfo.StockId)
                .FirstAsync();
            var seq = await _dailySequenceService.GetNextSequenceAsync();
            return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
        }
            if (stockDetail == null)
                throw new Exception("未找到对应的库存信息");
            // 2. æ¢å¤åº“存数量
            if (stockDetail.StockQuantity == 0)
        private async Task<Dt_OutStockLockInfo> CreateSplitLockInfo(Dt_OutStockLockInfo originalLock, decimal quantity, string newBarcode)
        {
            var newLockInfo = new Dt_OutStockLockInfo
            {
                // æ•´åŒ…出库的情况
                stockDetail.StockQuantity = cancelQty;
                stockDetail.OutboundQuantity = cancelQty;
            }
            else
                OrderNo = originalLock.OrderNo,
                OrderDetailId = originalLock.OrderDetailId,
                BatchNo = originalLock.BatchNo,
                MaterielCode = originalLock.MaterielCode,
                MaterielName = originalLock.MaterielName,
                StockId = originalLock.StockId,
                OrderQuantity = quantity,
                OriginalQuantity = quantity,
                AssignQuantity = quantity,
                PickedQty = quantity,
                LocationCode = originalLock.LocationCode,
                PalletCode = originalLock.PalletCode,
                TaskNum = originalLock.TaskNum,
                Status = (int)OutLockStockStatusEnum.拣选完成,
                Unit = originalLock.Unit,
                SupplyCode = originalLock.SupplyCode,
                OrderType = originalLock.OrderType,
                CurrentBarcode = newBarcode,
                OriginalLockQuantity = quantity,
                IsSplitted = 1,
                ParentLockId = originalLock.Id
            };
            var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync();
            newLockInfo.Id = newLockId;
            return newLockInfo;
        }
        private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal splitQty, decimal remainQty, string newBarcode)
        {
            var splitHistory = new Dt_SplitPackageRecord
            {
                // éƒ¨åˆ†å‡ºåº“的情况
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                FactoryArea = lockInfo.FactoryArea,
                TaskNum = lockInfo.TaskNum,
                OutStockLockInfoId = lockInfo.Id,
                StockId = stockDetail.StockId,
                Operator = App.User.UserName,
                IsReverted = false,
                OriginalBarcode = stockDetail.Barcode,
                NewBarcode = newBarcode,
                SplitQty = splitQty,
                RemainQuantity = remainQty,
                MaterielCode = lockInfo.MaterielCode,
                SplitTime = DateTime.Now,
                OrderNo = lockInfo.OrderNo,
                PalletCode = lockInfo.PalletCode,
                Status = (int)SplitPackageStatusEnum.已拣选
            };
            // 3. æ¢å¤é”å®šä¿¡æ¯çŠ¶æ€
            lockInfo.AssignQuantity += cancelQty;
            lockInfo.PickedQty -= cancelQty;
            await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
        }
            if (lockInfo.PickedQty == 0)
        private List<SplitResult> CreateSplitResults(Dt_OutStockLockInfo lockInfo, decimal splitQty, decimal remainQty, string newBarcode, string originalBarcode)
        {
            return new List<SplitResult>
        {
            new SplitResult
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
                materialCode = lockInfo.MaterielCode,
                supplierCode = lockInfo.SupplyCode,
                quantityTotal = splitQty.ToString("F2"),
                batchNumber = newBarcode,
                batch = lockInfo.BatchNo,
                factory = lockInfo.FactoryArea,
                date = DateTime.Now.ToString("yyyy-MM-dd"),
            },
            new SplitResult
            {
                materialCode = lockInfo.MaterielCode,
                supplierCode = lockInfo.SupplyCode,
                quantityTotal = remainQty.ToString("F2"),
                batchNumber = originalBarcode,
                batch = lockInfo.BatchNo,
                factory = lockInfo.FactoryArea,
                date = DateTime.Now.ToString("yyyy-MM-dd"),
            }
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
        };
        }
            // 4. å¤„理相关的拆包记录状态恢复
        private async Task UpdateSplitRecordsStatus(string barcode)
        {
            var relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OriginalBarcode == barcode &&
                           it.Status == (int)SplitPackageStatusEnum.已拣选)
                .Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode)
                .Where(it => !it.IsReverted)
                .ToListAsync();
            foreach (var record in relatedSplitRecords)
            {
                record.Status = (int)SplitPackageStatusEnum.已拆包;
                record.Status = (int)SplitPackageStatusEnum.已拣选;
                await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
            }
            // 5. æ¢å¤è®¢å•明细的拣选数量
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => it.PickedQty == it.PickedQty - cancelQty)
                .Where(it => it.Id == lockInfo.OrderDetailId)
                .ExecuteCommandAsync();
            // 6. æ¢å¤è®¢å•状态
            await CheckAndRevertOrderStatus(orderNo);
            // 7. åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>().Where(it => it.Id == pickingRecord.Id).ExecuteCommandAsync();
            //// 8. è®°å½•取消操作历史
            //await RecordCancelHistory(orderNo, palletCode, barcode, cancelQty, pickingRecord.Id,
            //    lockInfo.MaterielCode, "取消分拣");
        }
        /// <summary>
        /// æ£€æŸ¥å¹¶æ¢å¤è®¢å•状态
        /// </summary>
        private async Task CheckAndRevertOrderStatus(string orderNo)
        private async Task<int> GenerateTaskNumber()
        {
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
                .FirstAsync();
            return await _dailySequenceService.GetNextSequenceAsync();
        }
            if (order != null && order.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt())
        private WebResponseContent CreatePickingResponse(PickingResult result, string adjustedReason)
        {
            if (result.SplitResults.Any())
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => x.OrderStatus == OutOrderStatusEnum.出库中.ObjToInt())
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
        }
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
        {
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 1)
                .ToListAsync();
            return list.Where(x => x.RemainQuantity > 0).ToList();
        }
        // èŽ·å–å·²æ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode)
        {
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 6)
                .ToListAsync();
            return list;
        }
        // èŽ·å–æ‹£é€‰æ±‡æ€»
        public async Task<object> GetPickingSummary(ConfirmPickingDto dto)
        {
            var picked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
             .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
             .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
             .Where(x => x.Status == 6)
             .GroupBy(x => new { x.PalletCode, x.MaterielCode })
             .Select(x => new SummaryPickingDto
             {
                 PalletCode = x.PalletCode,
                 MaterielCode = x.MaterielCode,
                 pickedCount = SqlFunc.AggregateCount(x.Id)
             }).FirstAsync();
            if (picked == null)
            {
                picked = new SummaryPickingDto { pickedCount = 0 };
                return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = result.SplitResults });
            }
            var summary = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
                .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
                .Where(x => x.Status == 1)
                .GroupBy(x => new { x.PalletCode, x.MaterielCode })
                .Select(x => new SummaryPickingDto
                {
                    PalletCode = x.PalletCode,
                    MaterielCode = x.MaterielCode,
                    UnpickedCount = SqlFunc.AggregateCount(x.Id),
                    UnpickedQuantity = SqlFunc.AggregateSum(x.AssignQuantity) - SqlFunc.AggregateSum(x.PickedQty),
                }).FirstAsync();
            if (summary == null)
            {
                summary = new SummaryPickingDto { pickedCount = 0 };
            }
            summary.pickedCount = picked.pickedCount;
            return summary;
        }
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
        /// </summary>
        public async Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId)
        {
            // é€šè¿‡å‡ºåº“单ID查询相关的拣选历史
            // æ³¨æ„ï¼šDt_PickingRecord ä¸­æ²¡æœ‰ç›´æŽ¥å­˜å‚¨OrderId,需要通过出库单明细关联
            var detailIds = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .Where(d => d.OrderId == orderId)
                .Select(d => d.Id)
                .ToListAsync();
            return await Db.Queryable<Dt_PickingRecord>()
                .Where(p => detailIds.Contains(p.OrderDetailId))
                .OrderByDescending(p => p.PickTime)
                .ToListAsync();
            return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å‡ºåº“çŠ¶æ€ä¿¡æ¯
        /// </summary>
        public async Task<WebResponseContent> GetPalletOutboundStatus(string palletCode)
        {
            // èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.PalletCode == palletCode)
                .ToListAsync();
            // èŽ·å–æ‰˜ç›˜åº“å­˜ä¿¡æ¯
            var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .Includes(x => x.Details)
                .Where(x => x.PalletCode == palletCode)
                .FirstAsync();
            if (stockInfo == null)
                return WebResponseContent.Instance.Error("未找到托盘信息");
            // è®¡ç®—各种数量
            var totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
            var totalOutboundQuantity = stockInfo.Details.Sum(x => x.OutboundQuantity);
            var totalLockedQuantity = lockInfos.Where(x => x.Status == (int)OutLockStockStatusEnum.出库中)
                .Sum(x => x.AssignQuantity - x.PickedQty);
            var totalPickedQuantity = lockInfos.Sum(x => x.PickedQty);
            var result = new
            {
                PalletCode = palletCode,
                LocationCode = stockInfo.LocationCode,
                StockStatus = stockInfo.StockStatus,
                TotalStockQuantity = totalStockQuantity,
                TotalOutboundQuantity = totalOutboundQuantity,
                TotalLockedQuantity = totalLockedQuantity,
                TotalPickedQuantity = totalPickedQuantity,
                AvailableQuantity = totalStockQuantity - totalOutboundQuantity,
                LockInfos = lockInfos.Select(x => new
                {
                    x.Id,
                    x.MaterielCode,
                    x.OrderDetailId,
                    x.AssignQuantity,
                    x.PickedQty,
                    x.Status,
                    x.CurrentBarcode,
                    x.IsSplitted
                }).ToList(),
                StockDetails = stockInfo.Details.Select(x => new
                {
                    x.Barcode,
                    x.MaterielCode,
                    StockQuantity = x.StockQuantity,
                    OutboundQuantity = x.OutboundQuantity,
                    AvailableQuantity = x.StockQuantity - x.OutboundQuantity
                }).ToList()
            };
            return WebResponseContent.Instance.OK(null, result);
        }
        /// <summary>
        /// ç›´æŽ¥å‡ºåº“ - æ•´ä¸ªæ‰˜ç›˜å‡ºåº“,清空库存
        /// </summary>
        public async Task<WebResponseContent> DirectOutbound(DirectOutboundRequest request)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .Includes(x => x.Details)
                    .Where(x => x.PalletCode == request.PalletCode).FirstAsync();
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error("未找到托盘库存信息");
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == request.OrderNo && x.PalletCode == request.PalletCode)
                    .ToListAsync();
                foreach (var lockInfo in lockInfos)
                {
                    if (lockInfo.Status == (int)OutLockStockStatusEnum.出库中)
                    {
                        lockInfo.PickedQty = lockInfo.AssignQuantity;
                    }
                    lockInfo.Status = (int)OutLockStockStatusEnum.已出库;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == lockInfo.OrderDetailId)
                    .FirstAsync();
                    if (orderDetail != null)
                    {
                        orderDetail.OverOutQuantity += lockInfo.PickedQty;
                        orderDetail.LockQuantity -= lockInfo.PickedQty;
                        orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        orderDetail.LockQuantity = 0;
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                var groupDetails = lockInfos.GroupBy(x => x.OrderDetailId).Select(x => new
                {
                    OrderDetailId = x.Key,
                    TotalQuantity = x.Sum(o => o.PickedQty)
                }).ToList();
                foreach (var item in groupDetails)
                {
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().Where(x => x.Id == item.OrderDetailId).FirstAsync();
                    if (orderDetail != null)
                    {
                        orderDetail.OverOutQuantity = item.TotalQuantity;
                        orderDetail.LockQuantity = 0;
                        orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                await CheckAndUpdateOrderStatus(request.OrderNo);
                var lockInfoIds = lockInfos.Select(x => x.Id).ToList();
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => lockInfoIds.Contains(x.OutStockLockInfoId) &&
                               x.Status == (int)SplitPackageStatusEnum.已拆包)
                    .ToListAsync();
                foreach (var record in splitRecords)
                {
                    record.Status = (int)SplitPackageStatusEnum.已拣选;
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                var location = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                    .Where(x => x.LocationCode == stockInfo.LocationCode)
                    .FirstAsync();
                if (location != null)
                {
                    location.LocationStatus = (int)LocationStatusEnum.Free;
                    await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync();
                }
                foreach (var detail in stockInfo.Details)
                {
                    await _stockInfoDetailService.Db.Deleteable(detail).ExecuteCommandAsync();
                }
                await _stockInfoService.Db.Deleteable(stockInfo).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("直接出库成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"直接出库失败: {ex.Message}");
            }
        }
        #endregion
    }
    #region æ”¯æŒç±»å®šä¹‰
    #region è¿”回
    /// <summary>
    /// æ‹†åŒ…链项
    /// </summary>
    public class SplitChainItem
    public class ValidationResult<T>
    {
        public Dt_SplitPackageRecord SplitRecord { get; set; }
        public string OriginalBarcode { get; set; }
        public string NewBarcode { get; set; }
        public decimal SplitQuantity { get; set; }
        public bool IsValid { get; set; }
        public T Data { get; set; }
        public string ErrorMessage { get; set; }
        public static ValidationResult<T> Success(T data)
        {
            return new ValidationResult<T>
            {
                IsValid = true,
                Data = data
            };
        }
        public static ValidationResult<T> Error(string message)
        {
            return new ValidationResult<T>
            {
                IsValid = false,
                ErrorMessage = message
            };
        }
    }
    public  class ReturnAnalysisResult
    public class PickingResult
    {
        public Dt_OutStockLockInfo FinalLockInfo { get; set; }
        public string FinalBarcode { get; set; }
        public int FinalStockId { get; set; }
        public decimal ActualPickedQty { get; set; }
        public List<SplitResult> SplitResults { get; set; } = new List<SplitResult>();
    }
    public class ReturnAnalysisResult
    {
        public bool HasItemsToReturn { get; set; }
        public bool HasRemainingLocks { get; set; }
@@ -1999,5 +1449,4 @@
    }
    #endregion
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs
@@ -21,14 +21,7 @@
            _splitPackageService = splitPackageService;
            _outStockLockInfoService = outStockLockInfoService;
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å‡ºåº“çŠ¶æ€
        /// </summary>
        [HttpPost("GetPalletOutboundStatus")]
        public async Task<WebResponseContent> GetPalletOutboundStatus(string palletCode)
        {
            return await Service.GetPalletOutboundStatus(palletCode);
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
@@ -98,50 +91,13 @@
            return await Service.ReturnRemaining(dto.OrderNo, dto.PalletCode, "");
        }
        [HttpPost("direct-outbound")]
        public async Task<WebResponseContent> DirectOutbound([FromBody] DirectOutboundRequest dto)
        {
           return await Service.DirectOutbound(dto);
        }
        ///// <summary>
        ///// æ‹£é€‰ç¡®è®¤
        ///// </summary>
        //[HttpPost("ConfirmPicking")]
        //public async Task<WebResponseContent> ConfirmPicking([FromBody] PickingConfirmRequest request)
        //[HttpPost("direct-outbound")]
        //public async Task<WebResponseContent> DirectOutbound([FromBody] DirectOutboundRequest dto)
        //{
        //    return await Service.ConfirmPicking(request);
        //}
        /// <summary>
        /// éªŒè¯æ¡ç å¹¶èŽ·å–ç‰©æ–™ä¿¡æ¯
        /// </summary>
        [HttpGet("ValidateBarcode")]
        public async Task<WebResponseContent> ValidateBarcode(string barcode)
        {
            return await Service.ValidateBarcode(barcode);
        }
        //   return await Service.DirectOutbound(dto);
 
        ///// <summary>
        ///// ç›´æŽ¥å‡ºåº“
        ///// </summary>
        //[HttpPost("DirectOutbound")]
        //public async Task<WebResponseContent> DirectOutbound([FromBody] DirectOutboundRequest request)
        //{
        //    return await Service.DirectOutbound(request);
        //}
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
        /// </summary>
        [HttpGet("GetPickingHistory")]
        public async Task<WebResponseContent> GetPickingHistory(int orderId)
        {
            var history = await Service.GetPickingHistory(orderId);
            return WebResponseContent.Instance.OK(null, history);
        }
        //}
        /// <summary>
        /// æ’¤é”€æ‹£é€‰
        /// </summary>