pan
2025-11-21 482aa82a99419383848cabbdf135744259b17c77
提交
已修改1个文件
1055 ■■■■ 文件已修改
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 1055 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -209,7 +209,7 @@
                                details = new List<FeedbackOutboundDetailsModel>()
                            };
                            var lists = _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>().Where(x => x.OrderNo == orderNo).ToList();
                            var lists = _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>().Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
                            var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                               .Select(group => new FeedbackOutboundDetailsModel
@@ -226,7 +226,7 @@
                                       supplyCode = row.SupplyCode,
                                       batchNo = row.BatchNo,
                                       unit = row.Unit,
                                       qty = row.AssignQuantity
                                       qty = row.PickedQty
                                   }).ToList()
                               }).ToList();
                            feedmodel.details = groupedData;
@@ -700,294 +700,763 @@
            }
        }
        /// <summary>
        /// å›žåº“操作
        /// </summary>
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
                {
                    return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
                }
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .FirstAsync(x => x.PalletCode == palletCode);
                // èŽ·å–æ‰€æœ‰æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•ï¼ŒåŒ…æ‹¬æ‹†åŒ…äº§ç”Ÿçš„è®°å½•
                var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo && it.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
                var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
                var tasks = new List<Dt_Task>();
                // æŸ¥è¯¢ä»»åŠ¡è¡¨
                var task = remainingLocks.Any()
                    ? _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault()
                    : _taskRepository.QueryData(x => x.PalletCode == palletCode).FirstOrDefault();
                if (stockInfo == null)
                {
                    return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} å¯¹åº”的库存信息");
                }
                var task = await GetCurrentTask(orderNo, palletCode);
                if (task == null)
                {
                    return WebResponseContent.Instance.Error("未找到对应的任务信息");
                }
                // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–非出库货物(库存货物)
                var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(it => it.StockId == stockinfo.Id &&
                                (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
                                 it.Status == StockStatusEmun.入库完成.ObjToInt() ||
                                 it.Status == StockStatusEmun.出库锁定.ObjToInt()))
                    .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // æœªå®Œå…¨å‡ºåº“çš„
                    .ToListAsync();
                // åˆ†æžéœ€è¦å›žåº“的货物
                var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id);
                // æ£€æŸ¥æ‹†åŒ…记录,找出需要回库的条码
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                    .ToListAsync();
                // è®¡ç®—需要回库的拆包条码
                var splitBarcodesToReturn = new List<string>();
                foreach (var splitRecord in splitRecords)
                if (!returnAnalysis.HasItemsToReturn)
                {
                    // æ£€æŸ¥åŽŸæ¡ç æ˜¯å¦è¿˜æœ‰åº“å­˜éœ€è¦å›žåº“
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockinfo.Id)
                        .FirstAsync();
                    if (originalStock != null && originalStock.StockQuantity > 0)
                    {
                        splitBarcodesToReturn.Add(splitRecord.OriginalBarcode);
                    }
                    // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦è¿˜æœ‰åº“存需要回库
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockinfo.Id)
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        splitBarcodesToReturn.Add(splitRecord.NewBarcode);
                    }
                    return await HandleNoReturnItems(orderNo, palletCode);
                }
                // å¦‚果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物,也无拆包剩余货物)
                if (!remainingLocks.Any() && !palletStockGoods.Any() && !splitBarcodesToReturn.Any())
                {
                    // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
                    var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
                        .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
                // æ‰§è¡Œå›žåº“操作
                await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, returnAnalysis);
                    if (allPicked)
                    {
                        return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
                    }
                    else
                    {
                        return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
                    }
                }
                //创建回库任务并处理ESS
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, returnAnalysis);
                var firstlocation = _locationInfoService.Db.Queryable<Dt_LocationInfo>().First(x => x.LocationCode == task.SourceAddress);
                decimal totalReturnQty = 0;
                _unitOfWorkManage.CommitTran();
                // æƒ…况1:处理未分拣的出库锁定记录
                if (remainingLocks.Any(x => x.PalletCode == palletCode))
                {
                    var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
                    totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                // æ›´æ–°è®¢å•状态
                await UpdateOrderStatusForReturn(orderNo);
                    if (totalReturnQty > 0)
                    {
                        // åˆ†é…æ–°è´§ä½
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{returnAnalysis.TotalReturnQty}");
                        // æ›´æ–°å‡ºåº“锁定记录状态
                        var lockIds = palletLocks.Select(x => x.Id).ToList();
                        await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                            .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.回库中 })
                            .Where(it => lockIds.Contains(it.Id))
                            .ExecuteCommandAsync();
                        // å¤„理库存记录
                        foreach (var lockInfo in palletLocks)
                        {
                            decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                            // æ£€æŸ¥åº“存记录是否存在
                            var existingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                                .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                                .FirstAsync();
                            if (existingStock != null)
                            {
                                // åº“存记录存在,恢复锁定数量
                                existingStock.OutboundQuantity = 0;
                                await _stockInfoDetailService.Db.Updateable(existingStock).ExecuteCommandAsync();
                            }
                            else
                            {
                                // åº“存记录不存在(可能是拆包产生的新条码),创建新的库存记录
                                var newStockDetail = new Dt_StockInfoDetail
                                {
                                    StockId = lockInfo.StockId,
                                    MaterielCode = lockInfo.MaterielCode,
                                    MaterielName = lockInfo.MaterielName,
                                    OrderNo = lockInfo.OrderNo,
                                    BatchNo = lockInfo.BatchNo,
                                    StockQuantity = returnQty,
                                    OutboundQuantity = 0,
                                    Barcode = lockInfo.CurrentBarcode,
                                    InboundOrderRowNo = "",
                                    Status = StockStatusEmun.入库完成.ObjToInt(),
                                    SupplyCode = lockInfo.SupplyCode,
                                    WarehouseCode = lockInfo.WarehouseCode,
                                    Unit = lockInfo.Unit
                                };
                                await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                            }
                        }
                        // åˆ›å»ºå›žåº“任务
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                // æƒ…况2:处理拆包剩余的库存货物
                if (splitBarcodesToReturn.Any())
                {
                    decimal splitReturnQty = 0;
                    foreach (var barcode in splitBarcodesToReturn)
                    {
                        var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                            .Where(it => it.Barcode == barcode && it.StockId == stockinfo.Id)
                            .FirstAsync();
                        if (stockDetail != null && stockDetail.StockQuantity > 0)
                        {
                            splitReturnQty += stockDetail.StockQuantity;
                            // æ¢å¤åº“存状态为入库完成
                            stockDetail.OutboundQuantity = 0;
                            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                        }
                    }
                    totalReturnQty += splitReturnQty;
                    // å¦‚果没有创建任务,创建回库任务
                    if (!tasks.Any())
                    {
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                // æƒ…况3:出库货物已分拣完,但托盘上还有其他库存货物需要回库
                if (palletStockGoods.Any() && !remainingLocks.Any(x => x.PalletCode == palletCode))
                {
                    decimal otherReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
                    totalReturnQty += otherReturnQty;
                    // æ›´æ–°è¿™äº›åº“存货物的状态
                    foreach (var stockGood in palletStockGoods)
                    {
                        stockGood.OutboundQuantity = 0;
                        await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
                    }
                    // å¦‚果没有创建任务,创建回库任务
                    if (!tasks.Any())
                    {
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                var allSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                    .ToListAsync();
                foreach (var record in allSplitRecords)
                {
                    record.Status = (int)SplitPackageStatusEnum.已回库;
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                // ä¿å­˜ä»»åŠ¡ ç»™ESS下发任务
                if (tasks.Any())
                {
                    try
                    {
                        await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
                        var targetAddress = task.TargetAddress;
                        _taskRepository.DeleteData(task);
                        // ç»™ ESS æµåŠ¨ä¿¡å·å’Œåˆ›å»ºä»»åŠ¡
                        try
                        {
                            var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                            {
                                slotCode = movestations[targetAddress],
                                containerCode = palletCode
                            });
                            if (result)
                            {
                                TaskModel esstask = new TaskModel()
                                {
                                    taskType = "putaway",
                                    taskGroupCode = "",
                                    groupPriority = 0,
                                    tasks = new List<TasksType>
                            {
                                new()
                                {
                                    taskCode = tasks.First().TaskNum.ToString(),
                                    taskPriority = 0,
                                    taskDescribe = new TaskDescribeType {
                                        containerCode = palletCode,
                                        containerType = "CT_KUBOT_STANDARD",
                                        fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
                                        toStationCode = "",
                                        toLocationCode = tasks.First().TargetAddress,
                                        deadline = 0, storageTag = ""
                                    }
                                }
                            }
                                };
                                var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
                                _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž:  " + resulttask);
                            }
                        }
                        catch (Exception ex)
                        {
                            _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž catch err:  " + ex.Message);
                        }
                        _unitOfWorkManage.CommitTran();
                        return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
                    }
                    catch (Exception ex)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
                    }
                }
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error("未创建任何回库任务");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"ReturnRemaining失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
            }
        }
        #region è®¢å•状态
        private async Task UpdateOrderStatusForReturn(string orderNo)
        {
            try
            {
                var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                    .Where((o, item) => item.OrderNo == orderNo)
                    .Select((o, item) => o)
                    .ToListAsync();
                bool allCompleted = true;
                foreach (var detail in orderDetails)
                {
                    if (detail.OverOutQuantity < detail.NeedOutQuantity)
                    {
                        allCompleted = false;
                        break;
                    }
                }
                var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                    .FirstAsync(x => x.OrderNo == orderNo);
                if (outboundOrder == null) return;
                // åªæœ‰å½“状态确实发生变化时才更新
                int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    _logger.LogInformation($"回库操作更新订单状态 - OrderNo: {orderNo}, æ–°çŠ¶æ€: {newStatus}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"UpdateOrderStatusForReturn失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
        }
        #endregion
        #region Private Methods
        private async Task<Dt_Task> GetCurrentTask(string orderNo, string palletCode)
        {
            // å…ˆå°è¯•通过订单号和托盘号查找任务
            var task = await _taskRepository.Db.Queryable<Dt_Task>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .FirstAsync();
            if (task == null)
            {
                // å¦‚果找不到,再通过托盘号查找
                task = await _taskRepository.Db.Queryable<Dt_Task>()
                    .Where(x => x.PalletCode == palletCode)
                    .FirstAsync();
            }
            return task;
        }
        private async Task<ReturnAnalysisResult> AnalyzeReturnItems(string orderNo, string palletCode, int stockId)
        {
            var result = new ReturnAnalysisResult();
            // èŽ·å–æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•
            var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
                           it.Status == (int)OutLockStockStatusEnum.出库中)
                .ToListAsync();
            if (remainingLocks.Any())
            {
                result.HasRemainingLocks = true;
                result.RemainingLocks = remainingLocks;
                result.RemainingLocksReturnQty = remainingLocks.Sum(x => x.AssignQuantity - x.PickedQty);
            }
            // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–库存货物
            var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.StockId == stockId &&
                            (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
                             it.Status == StockStatusEmun.入库完成.ObjToInt() ||
                             it.Status == StockStatusEmun.出库锁定.ObjToInt()))
                .Where(it => it.StockQuantity > 0) // æœ‰åº“存的
                .ToListAsync();
            if (palletStockGoods.Any())
            {
                result.HasPalletStockGoods = true;
                result.PalletStockGoods = palletStockGoods;
                result.PalletStockReturnQty = palletStockGoods.Sum(x => x.StockQuantity);
            }
            //  æ£€æŸ¥æ‹†åŒ…记录
            var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                .ToListAsync();
            if (splitRecords.Any())
            {
                result.HasSplitRecords = true;
                result.SplitRecords = splitRecords;
                result.SplitReturnQty = await CalculateSplitReturnQuantity(splitRecords, stockId);
            }
            result.TotalReturnQty = result.RemainingLocksReturnQty + result.PalletStockReturnQty + result.SplitReturnQty;
            result.HasItemsToReturn = result.TotalReturnQty > 0;
            return result;
        }
        private async Task<decimal> CalculateSplitReturnQuantity(List<Dt_SplitPackageRecord> splitRecords, int stockId)
        {
            decimal totalQty = 0;
            var processedBarcodes = new HashSet<string>();
            foreach (var splitRecord in splitRecords)
            {
                // æ£€æŸ¥åŽŸæ¡ç 
                if (!processedBarcodes.Contains(splitRecord.OriginalBarcode))
                {
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockId)
                        .FirstAsync();
                    if (originalStock != null && originalStock.StockQuantity > 0)
                    {
                        totalQty += originalStock.StockQuantity;
                        processedBarcodes.Add(splitRecord.OriginalBarcode);
                    }
                }
                // æ£€æŸ¥æ–°æ¡ç 
                if (!processedBarcodes.Contains(splitRecord.NewBarcode))
                {
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockId)
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        totalQty += newStock.StockQuantity;
                        processedBarcodes.Add(splitRecord.NewBarcode);
                    }
                }
            }
            return totalQty;
        }
        private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode)
        {
            // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
            var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
                .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
            if (allPicked)
            {
                return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
            }
            else
            {
                return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
            }
        }
        private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo,
            Dt_Task task, ReturnAnalysisResult analysis)
        {
            // æƒ…况1:处理未分拣的出库锁定记录
            if (analysis.HasRemainingLocks)
            {
                await HandleRemainingLocksReturn(analysis.RemainingLocks, stockInfo.Id);
                // å…³é”®ï¼šæ›´æ–°è®¢å•明细的已拣选数量
                await UpdateOrderDetailsOnReturn(analysis.RemainingLocks);
            }
            // æƒ…况2:处理托盘上其他库存货物
            if (analysis.HasPalletStockGoods)
            {
                await HandlePalletStockGoodsReturn(analysis.PalletStockGoods);
            }
            // æƒ…况3:处理拆包记录
            if (analysis.HasSplitRecords)
            {
                await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode);
            }
            // æ›´æ–°åº“存主表状态
            await UpdateStockInfoStatus(stockInfo);
        }
        private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks, int stockId)
        {
            var lockIds = remainingLocks.Select(x => x.Id).ToList();
            // æ›´æ–°å‡ºåº“锁定记录状态为回库中
            await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                .SetColumns(it => new Dt_OutStockLockInfo
                {
                    Status = (int)OutLockStockStatusEnum.回库中
                })
                .Where(it => lockIds.Contains(it.Id))
                .ExecuteCommandAsync();
            // å¤„理库存记录
            foreach (var lockInfo in remainingLocks)
            {
                decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                // æŸ¥æ‰¾å¯¹åº”的库存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                    .FirstAsync();
                if (stockDetail != null)
                {
                    // æ¢å¤åº“存状态
                    stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
                    stockDetail.Status = StockStatusEmun.入库完成.ObjToInt();
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                }
                else
                {
                    // åˆ›å»ºæ–°çš„库存记录(拆包产生的新条码)
                    var newStockDetail = new Dt_StockInfoDetail
                    {
                        StockId = lockInfo.StockId,
                        MaterielCode = lockInfo.MaterielCode,
                        MaterielName = lockInfo.MaterielName,
                        OrderNo = lockInfo.OrderNo,
                        BatchNo = lockInfo.BatchNo,
                        StockQuantity = returnQty,
                        OutboundQuantity = 0,
                        Barcode = lockInfo.CurrentBarcode,
                        InboundOrderRowNo = "",
                        Status = StockStatusEmun.入库完成.ObjToInt(),
                        SupplyCode = lockInfo.SupplyCode,
                        WarehouseCode = lockInfo.WarehouseCode,
                        Unit = lockInfo.Unit
                    };
                    await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                }
            }
        }
        private async Task UpdateOrderDetailsOnReturn(List<Dt_OutStockLockInfo> remainingLocks)
        {
            // æŒ‰è®¢å•明细分组
            var orderDetailGroups = remainingLocks.GroupBy(x => x.OrderDetailId);
            foreach (var group in orderDetailGroups)
            {
                var orderDetailId = group.Key;
                var totalReturnQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
                // èŽ·å–å½“å‰è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == orderDetailId);
                if (orderDetail != null)
                {
                    // è°ƒæ•´å·²æ‹£é€‰æ•°é‡å’Œå·²å‡ºåº“数量
                    decimal newPickedQty = Math.Max(0, orderDetail.PickedQty - totalReturnQty);
                    decimal newOverOutQuantity = Math.Max(0, orderDetail.OverOutQuantity - totalReturnQty);
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                        .SetColumns(it => new Dt_OutboundOrderDetail
                        {
                            PickedQty = newPickedQty,
                            OverOutQuantity = newOverOutQuantity
                        })
                        .Where(it => it.Id == orderDetailId)
                        .ExecuteCommandAsync();
                }
            }
        }
        private async Task HandlePalletStockGoodsReturn(List<Dt_StockInfoDetail> palletStockGoods)
        {
            foreach (var stockGood in palletStockGoods)
            {
                // æ¢å¤åº“存状态
                stockGood.OutboundQuantity = 0;
                stockGood.Status = StockStatusEmun.入库完成.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
            }
        }
        private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, string orderNo, string palletCode)
        {
            // æ›´æ–°æ‹†åŒ…记录状态
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已回库
                })
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && !x.IsReverted)
                .ExecuteCommandAsync();
        }
        private async Task UpdateStockInfoStatus(Dt_StockInfo stockInfo)
        {
            // æ›´æ–°åº“存主表状态
            stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
            await _stockInfoService.Db.Updateable(stockInfo).ExecuteCommandAsync();
        }
        private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, ReturnAnalysisResult analysis)
        {
            var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                .FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
            // åˆ†é…æ–°è´§ä½ï¼ˆå›žåº“到存储位)
            var newLocation = _locationInfoService.AssignLocation(firstLocation.LocationType);
            Dt_Task returnTask = new()
            {
                CurrentAddress = stations[originalTask.TargetAddress],
                Grade = 0,
                PalletCode = palletCode,
                NextAddress = "",
                OrderNo = originalTask.OrderNo,
                Roadway = newLocation.RoadwayNo,
                SourceAddress = stations[originalTask.TargetAddress],
                TargetAddress = newLocation.LocationCode,
                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                TaskType = TaskTypeEnum.InPick.ObjToInt(),
                PalletType = originalTask.PalletType,
                WarehouseId = originalTask.WarehouseId,
            };
            // ä¿å­˜å›žåº“任务
            await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
            // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
              _taskRepository.Db.Deleteable(originalTask);
            // ç»™ ESS å‘送流动信号和创建任务
            await SendESSCommands(palletCode, originalTask, returnTask);
        }
        private async Task<int> GenerateTaskNumber()
        {
            return await _dailySequenceService.GetNextSequenceAsync();
        }
        private async Task SendESSCommands(string palletCode, Dt_Task originalTask, Dt_Task returnTask)
        {
            try
            {
                // 1. å‘送流动信号
                var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                {
                    slotCode = movestations[originalTask.TargetAddress],
                    containerCode = palletCode
                });
                if (moveResult)
                {
                    // 2. åˆ›å»ºå›žåº“任务
                    var essTask = new TaskModel()
                    {
                        taskType = "putaway",
                        taskGroupCode = "",
                        groupPriority = 0,
                        tasks = new List<TasksType>
                {
                    new()
                    {
                        taskCode = returnTask.TaskNum.ToString(),
                        taskPriority = 0,
                        taskDescribe = new TaskDescribeType
                        {
                            containerCode = palletCode,
                            containerType = "CT_KUBOT_STANDARD",
                            fromLocationCode = stations.GetValueOrDefault(originalTask.TargetAddress) ?? "",
                            toStationCode = "",
                            toLocationCode = returnTask.TargetAddress,
                            deadline = 0,
                            storageTag = ""
                        }
                    }
                }
                    };
                    var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                    _logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"ReturnRemaining ESS命令发送失败: {ex.Message}");
                throw new Exception($"ESS系统通信失败: {ex.Message}");
            }
        }
        #endregion
        /// <summary>
        /// å›žåº“操作
        /// </summary>
        //public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        //{
        //    try
        //    {
        //        _unitOfWorkManage.BeginTran();
        //        // èŽ·å–æ‰€æœ‰æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•ï¼ŒåŒ…æ‹¬æ‹†åŒ…äº§ç”Ÿçš„è®°å½•
        //        var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
        //            .Where(it => it.OrderNo == orderNo && it.Status == (int)OutLockStockStatusEnum.出库中)
        //            .ToListAsync();
        //        var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
        //        var tasks = new List<Dt_Task>();
        //        // æŸ¥è¯¢ä»»åŠ¡è¡¨
        //        var task = remainingLocks.Any()
        //            ? _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault()
        //            : _taskRepository.QueryData(x => x.PalletCode == palletCode).FirstOrDefault();
        //        if (task == null)
        //        {
        //            return WebResponseContent.Instance.Error("未找到对应的任务信息");
        //        }
        //        // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–非出库货物(库存货物)
        //        var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //            .Where(it => it.StockId == stockinfo.Id &&
        //                        (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
        //                         it.Status == StockStatusEmun.入库完成.ObjToInt() ||
        //                         it.Status == StockStatusEmun.出库锁定.ObjToInt()))
        //            .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // æœªå®Œå…¨å‡ºåº“çš„
        //            .ToListAsync();
        //        // æ£€æŸ¥æ‹†åŒ…记录,找出需要回库的条码
        //        var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
        //            .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
        //            .ToListAsync();
        //        // è®¡ç®—需要回库的拆包条码
        //        var splitBarcodesToReturn = new List<string>();
        //        foreach (var splitRecord in splitRecords)
        //        {
        //            // æ£€æŸ¥åŽŸæ¡ç æ˜¯å¦è¿˜æœ‰åº“å­˜éœ€è¦å›žåº“
        //            var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //                .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockinfo.Id)
        //                .FirstAsync();
        //            if (originalStock != null && originalStock.StockQuantity > 0)
        //            {
        //                splitBarcodesToReturn.Add(splitRecord.OriginalBarcode);
        //            }
        //            // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦è¿˜æœ‰åº“存需要回库
        //            var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //                .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockinfo.Id)
        //                .FirstAsync();
        //            if (newStock != null && newStock.StockQuantity > 0)
        //            {
        //                splitBarcodesToReturn.Add(splitRecord.NewBarcode);
        //            }
        //        }
        //        // å¦‚果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物,也无拆包剩余货物)
        //        if (!remainingLocks.Any() && !palletStockGoods.Any() && !splitBarcodesToReturn.Any())
        //        {
        //            // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
        //            var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
        //                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
        //                .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
        //            if (allPicked)
        //            {
        //                return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
        //            }
        //            else
        //            {
        //                return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
        //            }
        //        }
        //        var firstlocation = _locationInfoService.Db.Queryable<Dt_LocationInfo>().First(x => x.LocationCode == task.SourceAddress);
        //        decimal totalReturnQty = 0;
        //        // æƒ…况1:处理未分拣的出库锁定记录
        //        if (remainingLocks.Any(x => x.PalletCode == palletCode))
        //        {
        //            var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
        //            totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
        //            if (totalReturnQty > 0)
        //            {
        //                // åˆ†é…æ–°è´§ä½
        //                var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
        //                // æ›´æ–°å‡ºåº“锁定记录状态
        //                var lockIds = palletLocks.Select(x => x.Id).ToList();
        //                await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
        //                    .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.回库中 })
        //                    .Where(it => lockIds.Contains(it.Id))
        //                    .ExecuteCommandAsync();
        //                // å¤„理库存记录
        //                foreach (var lockInfo in palletLocks)
        //                {
        //                    decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
        //                    // æ£€æŸ¥åº“存记录是否存在
        //                    var existingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //                        .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
        //                        .FirstAsync();
        //                    if (existingStock != null)
        //                    {
        //                        // åº“存记录存在,恢复锁定数量
        //                        existingStock.OutboundQuantity = 0;
        //                        await _stockInfoDetailService.Db.Updateable(existingStock).ExecuteCommandAsync();
        //                    }
        //                    else
        //                    {
        //                        // åº“存记录不存在(可能是拆包产生的新条码),创建新的库存记录
        //                        var newStockDetail = new Dt_StockInfoDetail
        //                        {
        //                            StockId = lockInfo.StockId,
        //                            MaterielCode = lockInfo.MaterielCode,
        //                            MaterielName = lockInfo.MaterielName,
        //                            OrderNo = lockInfo.OrderNo,
        //                            BatchNo = lockInfo.BatchNo,
        //                            StockQuantity = returnQty,
        //                            OutboundQuantity = 0,
        //                            Barcode = lockInfo.CurrentBarcode,
        //                            InboundOrderRowNo = "",
        //                            Status = StockStatusEmun.入库完成.ObjToInt(),
        //                            SupplyCode = lockInfo.SupplyCode,
        //                            WarehouseCode = lockInfo.WarehouseCode,
        //                            Unit = lockInfo.Unit
        //                        };
        //                        await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
        //                    }
        //                }
        //                // åˆ›å»ºå›žåº“任务
        //                CreateReturnTask(tasks, task, palletCode, newLocation);
        //            }
        //        }
        //        // æƒ…况2:处理拆包剩余的库存货物
        //        if (splitBarcodesToReturn.Any())
        //        {
        //            decimal splitReturnQty = 0;
        //            foreach (var barcode in splitBarcodesToReturn)
        //            {
        //                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        //                    .Where(it => it.Barcode == barcode && it.StockId == stockinfo.Id)
        //                    .FirstAsync();
        //                if (stockDetail != null && stockDetail.StockQuantity > 0)
        //                {
        //                    splitReturnQty += stockDetail.StockQuantity;
        //                    // æ¢å¤åº“存状态为入库完成
        //                    stockDetail.OutboundQuantity = 0;
        //                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
        //                }
        //            }
        //            totalReturnQty += splitReturnQty;
        //            // å¦‚果没有创建任务,创建回库任务
        //            if (!tasks.Any())
        //            {
        //                var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
        //                CreateReturnTask(tasks, task, palletCode, newLocation);
        //            }
        //        }
        //        // æƒ…况3:出库货物已分拣完,但托盘上还有其他库存货物需要回库
        //        if (palletStockGoods.Any() && !remainingLocks.Any(x => x.PalletCode == palletCode))
        //        {
        //            decimal otherReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
        //            totalReturnQty += otherReturnQty;
        //            // æ›´æ–°è¿™äº›åº“存货物的状态
        //            foreach (var stockGood in palletStockGoods)
        //            {
        //                stockGood.OutboundQuantity = 0;
        //                await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
        //            }
        //            // å¦‚果没有创建任务,创建回库任务
        //            if (!tasks.Any())
        //            {
        //                var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
        //                CreateReturnTask(tasks, task, palletCode, newLocation);
        //            }
        //        }
        //        var allSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
        //            .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
        //            .ToListAsync();
        //        foreach (var record in allSplitRecords)
        //        {
        //            record.Status = (int)SplitPackageStatusEnum.已回库;
        //            await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
        //        }
        //        // ä¿å­˜ä»»åŠ¡ ç»™ESS下发任务
        //        if (tasks.Any())
        //        {
        //            try
        //            {
        //                await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
        //                var targetAddress = task.TargetAddress;
        //                _taskRepository.DeleteData(task);
        //                // ç»™ ESS æµåŠ¨ä¿¡å·å’Œåˆ›å»ºä»»åŠ¡
        //                try
        //                {
        //                    var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
        //                    {
        //                        slotCode = movestations[targetAddress],
        //                        containerCode = palletCode
        //                    });
        //                    if (result)
        //                    {
        //                        TaskModel esstask = new TaskModel()
        //                        {
        //                            taskType = "putaway",
        //                            taskGroupCode = "",
        //                            groupPriority = 0,
        //                            tasks = new List<TasksType>
        //                    {
        //                        new()
        //                        {
        //                            taskCode = tasks.First().TaskNum.ToString(),
        //                            taskPriority = 0,
        //                            taskDescribe = new TaskDescribeType {
        //                                containerCode = palletCode,
        //                                containerType = "CT_KUBOT_STANDARD",
        //                                fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
        //                                toStationCode = "",
        //                                toLocationCode = tasks.First().TargetAddress,
        //                                deadline = 0, storageTag = ""
        //                            }
        //                        }
        //                    }
        //                        };
        //                        var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
        //                        _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž:  " + resulttask);
        //                    }
        //                }
        //                catch (Exception ex)
        //                {
        //                    _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž catch err:  " + ex.Message);
        //                }
        //                _unitOfWorkManage.CommitTran();
        //                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
        //            }
        //            catch (Exception ex)
        //            {
        //                _unitOfWorkManage.RollbackTran();
        //                return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
        //            }
        //        }
        //        _unitOfWorkManage.RollbackTran();
        //        return WebResponseContent.Instance.Error("未创建任何回库任务");
        //    }
        //    catch (Exception ex)
        //    {
        //        _unitOfWorkManage.RollbackTran();
        //        return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
        //    }
        //}
        /// <summary>
@@ -1262,38 +1731,9 @@
            }
        }
        /// <summary>
        /// è®°å½•取消操作历史
        /// </summary>
        private async Task RecordCancelHistory(string orderNo, string palletCode, string barcode,
            decimal cancelQty, int pickingRecordId, string materielCode, string reason)
        {
            //var cancelHistory = new Dt_PickingCancelRecord
            //{
            //    OrderNo = orderNo,
            //    PalletCode = palletCode,
            //    Barcode = barcode,
            //    CancelQuantity = cancelQty,
            //    CancelTime = DateTime.Now,
            //    Operator = App.User.UserName,
            //    OriginalPickingRecordId = pickingRecordId,
            //    MaterielCode = materielCode,
            //    Reason = reason
            //};
            //await Db.Insertable(cancelHistory).ExecuteCommandAsync();
        }
        /// <summary>
        /// æ‹†åŒ…链项
        /// </summary>
        public class SplitChainItem
        {
            public Dt_SplitPackageRecord SplitRecord { get; set; }
            public string OriginalBarcode { get; set; }
            public string NewBarcode { get; set; }
            public decimal SplitQuantity { get; set; }
        }
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
        {
@@ -1537,4 +1977,33 @@
        }
    }
    #region è¿”回
    /// <summary>
    /// æ‹†åŒ…链项
    /// </summary>
    public class SplitChainItem
    {
        public Dt_SplitPackageRecord SplitRecord { get; set; }
        public string OriginalBarcode { get; set; }
        public string NewBarcode { get; set; }
        public decimal SplitQuantity { get; set; }
    }
    public  class ReturnAnalysisResult
    {
        public bool HasItemsToReturn { get; set; }
        public bool HasRemainingLocks { get; set; }
        public bool HasPalletStockGoods { get; set; }
        public bool HasSplitRecords { get; set; }
        public decimal RemainingLocksReturnQty { get; set; }
        public decimal PalletStockReturnQty { get; set; }
        public decimal SplitReturnQty { get; set; }
        public decimal TotalReturnQty { get; set; }
        public List<Dt_OutStockLockInfo> RemainingLocks { get; set; } = new List<Dt_OutStockLockInfo>();
        public List<Dt_StockInfoDetail> PalletStockGoods { get; set; } = new List<Dt_StockInfoDetail>();
        public List<Dt_SplitPackageRecord> SplitRecords { get; set; } = new List<Dt_SplitPackageRecord>();
    }
    #endregion
}