pan
10 天以前 c95029139c278f0d2980e6d67cab42f5331e8d06
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundBatchPickingService.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging;
using SqlSugar;
using SqlSugar.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,14 +10,18 @@
using WIDESEA_Common.CommonEnum;
using WIDESEA_Common.OrderEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Outbound;
using WIDESEA_IAllocateService;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_ITaskInfoService;
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Basic;
using WIDESEA_Model.Models.Outbound;
@@ -45,6 +50,7 @@
        private readonly IDailySequenceService _dailySequenceService;
        private readonly IAllocateService _allocateService;
        private readonly IRepository<Dt_OutboundBatch> _outboundBatchRepository;
        private readonly ITask_HtyService _task_HtyService;
        private readonly ILogger<OutboundPickingService> _logger;
        private Dictionary<string, string> stations = new Dictionary<string, string>
@@ -64,7 +70,7 @@
        public OutboundBatchPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService,
            IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService,
            IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService,
            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_OutboundBatch> outboundBatchRepository) : base(BaseDal)
            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_OutboundBatch> outboundBatchRepository, ITask_HtyService task_HtyService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -82,6 +88,7 @@
            _dailySequenceService = dailySequenceService;
            _allocateService = allocateService;
            _outboundBatchRepository = outboundBatchRepository;
            _task_HtyService = task_HtyService;
        }
        // <summary>
@@ -216,7 +223,7 @@
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.CurrentBarcode == barcode
                        //&& x.Status == (int)OutLockStockStatusEnum.出库中
                          //&& x.Status == (int)OutLockStockStatusEnum.出库中
                          )
                .FirstAsync();
@@ -240,166 +247,6 @@
        #endregion
        #region å–走空箱逻辑
        /// <summary>
        /// å–走空箱 - æ¸…理已完成拣选的托盘数据
        /// </summary>
        public async Task<WebResponseContent> RemoveEmptyPallet(string orderNo, string palletCode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // éªŒè¯æ‰˜ç›˜æ˜¯å¦å¯ä»¥å–走(必须全部完成拣选)
                var validationResult = await ValidateEmptyPalletRemoval(orderNo, palletCode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var completedLocks = validationResult.Data;
                // æ¸…理锁定记录(标记为已完成)
                await CleanupCompletedLocks(completedLocks);
                // æ›´æ–°ç›¸å…³è®¢å•状态
                await UpdateOrderStatusAfterPalletRemoval(orderNo);
                // è®°å½•操作历史
                await RecordEmptyPalletRemoval(orderNo, palletCode, completedLocks);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取走空箱成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"取走空箱失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取走空箱失败:{ex.Message}");
            }
        }
        /// <summary>
        /// éªŒè¯ç©ºç®±å–走条件
        /// </summary>
        private async Task<ValidationResult<List<Dt_OutStockLockInfo>>> ValidateEmptyPalletRemoval(string orderNo, string palletCode)
        {
            // èŽ·å–æ‰˜ç›˜çš„æ‰€æœ‰é”å®šè®°å½•
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .ToListAsync();
            if (!lockInfos.Any())
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有锁定记录");
            // æ£€æŸ¥æ˜¯å¦æœ‰æœªå®Œæˆçš„锁定记录
            var unfinishedLocks = lockInfos.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.出库中 ||
                x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
            if (unfinishedLocks.Any())
            {
                var unfinishedCount = unfinishedLocks.Count;
                var unfinishedQty = unfinishedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
                    $"托盘还有{unfinishedCount}条未完成记录,剩余数量{unfinishedQty},不能取走空箱");
            }
            // èŽ·å–å·²å®Œæˆçš„é”å®šè®°å½•
            var completedLocks = lockInfos.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
            if (!completedLocks.Any())
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有已完成拣选的记录");
            return ValidationResult<List<Dt_OutStockLockInfo>>.Success(completedLocks);
        }
        /// <summary>
        /// æ¸…理已完成的锁定记录
        /// </summary>
        private async Task CleanupCompletedLocks(List<Dt_OutStockLockInfo> completedLocks)
        {
            foreach (var lockInfo in completedLocks)
            {
                // æ ‡è®°é”å®šè®°å½•为已取走
                lockInfo.Status = (int)OutLockStockStatusEnum.已取走;
                lockInfo.Operator = App.User.UserName;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                // æ¸…理对应的库存记录状态
                await CleanupStockInfo(lockInfo);
            }
        }
        /// <summary>
        /// æ¸…理库存信息
        /// </summary>
        private async Task CleanupStockInfo(Dt_OutStockLockInfo lockInfo)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
            if (stockDetail != null && stockDetail.Status == (int)StockStatusEmun.出库完成)
            {
                stockDetail.Status = (int)StockStatusEmun.已清理;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// æ›´æ–°è®¢å•状态
        /// </summary>
        private async Task UpdateOrderStatusAfterPalletRemoval(string orderNo)
        {
            // æ£€æŸ¥è®¢å•是否所有托盘都已完成
            var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo)
                .ToListAsync();
            var unfinishedPallets = allLocks
                .GroupBy(x => x.PalletCode)
                .Where(g => g.Any(x => x.Status == (int)OutLockStockStatusEnum.出库中 ||
                                      x.Status == (int)OutLockStockStatusEnum.回库中))
                .ToList();
            // å¦‚果没有未完成的托盘,更新订单状态为出库完成
            if (!unfinishedPallets.Any())
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => new Dt_OutboundOrder
                    {
                        OrderStatus = (int)OutOrderStatusEnum.出库完成,
                    })
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// è®°å½•空箱取走历史
        /// </summary>
        private async Task RecordEmptyPalletRemoval(string orderNo, string palletCode, List<Dt_OutStockLockInfo> completedLocks)
        {
            var removalRecord = new Dt_EmptyPalletRemoval
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                RemovalTime = DateTime.Now,
                Operator = App.User.UserName,
                CompletedItemsCount = completedLocks.Count,
                TotalPickedQuantity = completedLocks.Sum(x => x.PickedQty)
            };
            await Db.Insertable(removalRecord).ExecuteCommandAsync();
        }
        #endregion
        #region åˆ†æ‰¹åˆ†æ‹£
        /// <summary>
@@ -411,32 +258,60 @@
            {
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯åˆ†æ‹£è¯·æ±‚
                // éªŒè¯åˆ†æ‹£è¯·æ±‚
                var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (lockInfo, orderDetail, stockDetail, batch) = validationResult.Data;
                // ä½¿ç”¨é”å®šä¿¡æ¯çš„分配数量作为实际分拣数量
                var actualPickedQty = lockInfo.AssignQuantity;
                // æ£€æŸ¥æ˜¯å¦éœ€è¦è‡ªåŠ¨æ‹†åŒ…
                var autoSplitResult = await CheckAndAutoSplitIfNeeded(lockInfo, stockDetail, palletCode);
                if (autoSplitResult != null)
                {
                    // å¦‚果执行了自动拆包,重新获取最新的锁定信息和库存信息
                    var refreshedValidation = await ValidatePickingRequest(orderNo, palletCode, barcode);
                    if (!refreshedValidation.IsValid)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error(refreshedValidation.ErrorMessage);
                    }
                // 2. æ‰§è¡Œåˆ†æ‹£é€»è¾‘
                var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
                    (lockInfo, orderDetail, stockDetail, batch) = refreshedValidation.Data;
                // 3. æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据
                await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo);
                // 4. è®°å½•拣选历史
                await RecordPickingHistory(pickingResult, orderNo, palletCode);
                    var actualPickedQty = lockInfo.AssignQuantity;
                    var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
                    // æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据
                    await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo);
                    // è®°å½•拣选历史
                    await RecordPickingHistory(pickingResult, orderNo, palletCode);
                    _unitOfWorkManage.CommitTran();
                    return WebResponseContent.Instance.OK("自动拆包并分拣成功", autoSplitResult);
                }
                // æ­£å¸¸åˆ†æ‹£æµç¨‹ï¼ˆä¸éœ€è¦è‡ªåŠ¨æ‹†åŒ…ï¼‰
                var normalPickedQty = lockInfo.AssignQuantity;
                var normalPickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, normalPickedQty);
                // æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据
                await UpdateBatchAndOrderData(batch, orderDetail, normalPickedQty, orderNo);
                // è®°å½•拣选历史
                await RecordPickingHistory(normalPickingResult, orderNo, palletCode);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("分拣成功", new
                {
                    PickedQuantity = actualPickedQty,
                    PickedQuantity = normalPickedQty,
                    Barcode = barcode,
                    MaterialCode = lockInfo.MaterielCode
                    MaterialCode = lockInfo.MaterielCode,
                    AutoSplitted = false
                });
            }
            catch (Exception ex)
@@ -492,7 +367,179 @@
        }
        #endregion
        #region æ‰‹åŠ¨æ‹†åŒ…
        #region å–走空箱逻辑
        /// <summary>
        /// éªŒè¯ç©ºç®±å–走条件
        /// </summary>
        private async Task<ValidationResult<List<Dt_OutStockLockInfo>>> ValidateEmptyPalletRemoval(string orderNo, string palletCode)
        {
            // èŽ·å–æ‰˜ç›˜çš„æ‰€æœ‰é”å®šè®°å½•
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .ToListAsync();
            if (!lockInfos.Any())
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有锁定记录");
            // æ£€æŸ¥æ˜¯å¦æœ‰æœªå®Œæˆçš„锁定记录
            var unfinishedLocks = lockInfos.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.出库中 ||
                x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
            if (unfinishedLocks.Any())
            {
                var unfinishedCount = unfinishedLocks.Count;
                var unfinishedQty = unfinishedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
                    $"托盘还有{unfinishedCount}条未完成记录,剩余数量{unfinishedQty},不能取走空箱");
            }
            // æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦è¿˜æœ‰åº“存货物
            var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .FirstAsync(x => x.PalletCode == palletCode);
            if (stockInfo != null)
            {
                var remainingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockInfo.Id &&
                               x.Status == (int)StockStatusEmun.出库锁定 &&
                               x.StockQuantity > 0)
                    .ToListAsync();
                if (remainingStock.Any())
                {
                    var remainingQty = remainingStock.Sum(x => x.StockQuantity);
                    return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
                        $"托盘上还有库存货物,数量{remainingQty},不能取走空箱");
                }
            }
            // èŽ·å–å·²å®Œæˆçš„é”å®šè®°å½•
            var completedLocks = lockInfos.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
            if (!completedLocks.Any())
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有已完成拣选的记录");
            return ValidationResult<List<Dt_OutStockLockInfo>>.Success(completedLocks);
        }
        /// <summary>
        /// æ¸…理已完成的锁定记录
        /// </summary>
        private async Task CleanupCompletedLocks(List<Dt_OutStockLockInfo> completedLocks)
        {
            foreach (var lockInfo in completedLocks)
            {
                // æ ‡è®°é”å®šè®°å½•为已取走
                lockInfo.Status = (int)OutLockStockStatusEnum.已取走;
                lockInfo.Operator = App.User.UserName;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                // æ¸…理对应的库存记录状态
                await CleanupStockInfo(lockInfo);
            }
        }
        /// <summary>
        /// æ¸…理库存信息
        /// </summary>
        private async Task CleanupStockInfo(Dt_OutStockLockInfo lockInfo)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
            if (stockDetail != null && stockDetail.Status == (int)StockStatusEmun.出库完成)
            {
                stockDetail.Status = (int)StockStatusEmun.已清理;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// æ›´æ–°è®¢å•状态
        /// </summary>
        private async Task UpdateOrderStatusAfterPalletRemoval(string orderNo)
        {
            // æ£€æŸ¥è®¢å•是否所有托盘都已完成
            var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo)
                .ToListAsync();
            var unfinishedPallets = allLocks
                .GroupBy(x => x.PalletCode)
                .Where(g => g.Any(x => x.Status == (int)OutLockStockStatusEnum.出库中 ||
                                      x.Status == (int)OutLockStockStatusEnum.回库中))
                .ToList();
            // å¦‚果没有未完成的托盘,更新订单状态为出库完成
            if (!unfinishedPallets.Any())
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => new Dt_OutboundOrder
                    {
                        OrderStatus = (int)OutOrderStatusEnum.出库完成,
                    })
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// è®°å½•空箱取走历史
        /// </summary>
        private async Task RecordEmptyPalletRemoval(string orderNo, string palletCode, List<Dt_OutStockLockInfo> completedLocks)
        {
            var removalRecord = new Dt_EmptyPalletRemoval
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                RemovalTime = DateTime.Now,
                Operator = App.User.UserName,
                CompletedItemsCount = completedLocks.Count,
                TotalPickedQuantity = completedLocks.Sum(x => x.PickedQty)
            };
            await Db.Insertable(removalRecord).ExecuteCommandAsync();
        }
        #endregion
        private List<SplitResult> CreateSplitResults(Dt_OutStockLockInfo lockInfo, decimal splitQty, decimal remainQty, string newBarcode, string originalBarcode)
        {
            return new List<SplitResult>
        {
            new SplitResult
            {
                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"),
            }
        };
        }
        #region æ‰‹åŠ¨æ‹†åŒ…
        /// <summary>
        /// æ‰‹åŠ¨æ‹†åŒ…
@@ -503,24 +550,19 @@
            {
                _unitOfWorkManage.BeginTran();
                //  éªŒè¯æ‹†åŒ…请求
                // éªŒè¯æ‹†åŒ…请求
                var validationResult = await ValidateSplitRequest(orderNo, palletCode, originalBarcode, splitQuantity);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (lockInfo, stockDetail) = validationResult.Data;
                // . æ‰§è¡Œæ‹†åŒ…逻辑
                var splitResult = await ExecuteSplitLogic(lockInfo, stockDetail, splitQuantity, palletCode);
                // æ‰§è¡Œæ‹†åŒ…逻辑
                var splitResult = await ExecuteManualSplitLogic(lockInfo, stockDetail, splitQuantity, palletCode);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("手动拆包成功", new
                {
                    NewBarcode = splitResult.NewBarcode,
                    OriginalBarcode = originalBarcode,
                    SplitQuantity = splitQuantity
                });
                return WebResponseContent.Instance.OK("手动拆包成功", splitResult);
            }
            catch (Exception ex)
            {
@@ -530,9 +572,247 @@
            }
        }
        /// <summary>
        /// éªŒè¯æ‹†åŒ…请求 - å¢žå¼ºåˆ†é…æ•°é‡æŽ§åˆ¶
        /// </summary>
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateSplitRequest(
            string orderNo, string palletCode, string originalBarcode, decimal splitQuantity)
        {
            _logger.LogInformation($"开始验证拆包请求 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, åŽŸæ¡ç : {originalBarcode}, æ‹†åŒ…数量: {splitQuantity}");
            // æŸ¥æ‰¾é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.CurrentBarcode == originalBarcode &&
                           x.Status == (int)OutLockStockStatusEnum.出库中)
                .FirstAsync();
            if (lockInfo == null)
            {
                _logger.LogWarning($"未找到有效的锁定信息 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, æ¡ç : {originalBarcode}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到有效的锁定信息");
            }
            _logger.LogInformation($"找到锁定信息 - åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}, å·²æ‹£é€‰: {lockInfo.PickedQty}, è®¢å•数量: {lockInfo.OrderQuantity}");
            // èŽ·å–è®¢å•æ˜Žç»†
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
            if (orderDetail == null)
            {
                _logger.LogWarning($"未找到订单明细 - OrderDetailId: {lockInfo.OrderDetailId}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到订单明细");
            }
            _logger.LogInformation($"找到订单明细 - å·²åˆ†é…æ•°é‡: {orderDetail.AllocatedQuantity}, é”å®šæ•°é‡: {orderDetail.LockQuantity}");
            // èŽ·å–åº“å­˜ä¿¡æ¯
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == originalBarcode && x.StockId == lockInfo.StockId);
            if (stockDetail == null)
            {
                _logger.LogWarning($"未找到库存信息 - æ¡ç : {originalBarcode}, StockId: {lockInfo.StockId}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到对应的库存信息");
            }
            _logger.LogInformation($"找到库存信息 - åº“存数量: {stockDetail.StockQuantity}, å‡ºåº“数量: {stockDetail.OutboundQuantity}");
            // éªŒè¯æ‹†åŒ…数量不能大于库存数量
            if (stockDetail.StockQuantity < splitQuantity)
            {
                _logger.LogWarning($"拆包数量大于库存数量 - æ‹†åŒ…数量: {splitQuantity}, åº“存数量: {stockDetail.StockQuantity}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error($"拆包数量不能大于库存数量,当前库存:{stockDetail.StockQuantity}");
            }
            // éªŒè¯æ‹†åŒ…数量不能大于锁定信息的分配数量
            if (lockInfo.AssignQuantity < splitQuantity)
            {
                _logger.LogWarning($"拆包数量大于分配数量 - æ‹†åŒ…数量: {splitQuantity}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error($"拆包数量不能大于分配数量,当前分配数量:{lockInfo.AssignQuantity}");
            }
            // éªŒè¯æ‹†åŒ…数量不能大于锁定信息的未拣选数量
            decimal remainingToPick = lockInfo.AssignQuantity - lockInfo.PickedQty;
            if (remainingToPick < splitQuantity)
            {
                _logger.LogWarning($"拆包数量大于未拣选数量 - æ‹†åŒ…数量: {splitQuantity}, æœªæ‹£é€‰æ•°é‡: {remainingToPick}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error($"拆包数量不能大于未拣选数量,当前未拣选:{remainingToPick}");
            }
            // éªŒè¯æ‹†åŒ…后原锁定信息的分配数量不会为负数
            if (lockInfo.AssignQuantity - splitQuantity < 0)
            {
                _logger.LogWarning($"拆包后分配数量为负数 - å½“前分配数量: {lockInfo.AssignQuantity}, æ‹†åŒ…数量: {splitQuantity}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error($"拆包后分配数量不能为负数");
            }
            // éªŒè¯è®¢å•明细的分配数量是否足够
            // æ³¨æ„ï¼šæ‰‹åŠ¨æ‹†åŒ…ä¸ä¼šæ”¹å˜è®¢å•æ˜Žç»†çš„åˆ†é…æ•°é‡ï¼Œå› ä¸ºæ€»åˆ†é…æ•°é‡ä¸å˜
            // åªæ˜¯ä»Žä¸€ä¸ªé”å®šä¿¡æ¯è½¬ç§»åˆ°å¦ä¸€ä¸ªé”å®šä¿¡æ¯
            decimal totalLockAssignQuantity = await GetTotalLockAssignQuantity(orderDetail.Id);
            if (orderDetail.AllocatedQuantity != totalLockAssignQuantity)
            {
                _logger.LogWarning($"订单明细分配数量与锁定信息不一致 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQuantity}");
                // è¿™é‡Œä¸ç›´æŽ¥è¿”回错误,因为拆包操作本身不会导致不一致,只是记录警告
            }
            _logger.LogInformation($"拆包验证通过 - åŽŸæ¡ç : {originalBarcode}, æ‹†åŒ…数量: {splitQuantity}");
            return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((lockInfo, stockDetail));
        }
        /// <summary>
        /// èŽ·å–è®¢å•æ˜Žç»†çš„æ‰€æœ‰é”å®šä¿¡æ¯çš„æ€»åˆ†é…æ•°é‡
        /// </summary>
        private async Task<decimal> GetTotalLockAssignQuantity(long orderDetailId)
        {
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId)
                .ToListAsync();
            return lockInfos.Sum(x => x.AssignQuantity);
        }
        /// <summary>
        /// æ‰§è¡Œæ‰‹åŠ¨æ‹†åŒ…é€»è¾‘ - å¢žå¼ºåˆ†é…æ•°é‡æŽ§åˆ¶
        /// </summary>
        private async Task<List<SplitResult>> ExecuteManualSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal splitQuantity, string palletCode)
        {
            _logger.LogInformation($"开始执行手动拆包逻辑 - åŽŸæ¡ç : {stockDetail.Barcode}, æ‹†åŒ…数量: {splitQuantity}");
            // éªŒè¯æ‹†åŒ…数量
            if (lockInfo.AssignQuantity < splitQuantity)
            {
                throw new InvalidOperationException($"拆包数量超过锁定信息分配数量,拆包数量: {splitQuantity}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}");
            }
            // ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
            // è®¡ç®—剩余数量
            decimal remainQty = lockInfo.AssignQuantity - splitQuantity;
            // åˆ›å»ºæ–°åº“存明细
            var newStockDetail = new Dt_StockInfoDetail
            {
                StockId = stockDetail.StockId,
                MaterielCode = stockDetail.MaterielCode,
                OrderNo = stockDetail.OrderNo,
                BatchNo = stockDetail.BatchNo,
                StockQuantity = splitQuantity,
                OutboundQuantity = 0,
                Barcode = newBarcode,
                Status = (int)StockStatusEmun.出库锁定,
                SupplyCode = stockDetail.SupplyCode,
                Unit = stockDetail.Unit,
                BarcodeQty = stockDetail.BarcodeQty,
                BarcodeUnit = stockDetail.BarcodeUnit,
                BusinessType = stockDetail.BusinessType,
                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
            };
            await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
            // æ›´æ–°åŽŸåº“å­˜æ˜Žç»†
            stockDetail.StockQuantity -= splitQuantity;
            if (stockDetail.StockQuantity < 0) stockDetail.StockQuantity = 0;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // åˆ›å»ºæ–°é”å®šä¿¡æ¯
            var newLockInfo = new Dt_OutStockLockInfo
            {
                OrderNo = lockInfo.OrderNo,
                OrderDetailId = lockInfo.OrderDetailId, // ç»‘定到同一个订单明细
                OutboundBatchNo = lockInfo.OutboundBatchNo,
                MaterielCode = lockInfo.MaterielCode,
                MaterielName = lockInfo.MaterielName,
                StockId = lockInfo.StockId,
                OrderQuantity = splitQuantity,
                AssignQuantity = splitQuantity,
                PickedQty = 0,
                LocationCode = lockInfo.LocationCode,
                PalletCode = lockInfo.PalletCode,
                TaskNum = lockInfo.TaskNum,
                Status = (int)OutLockStockStatusEnum.出库中,
                Unit = lockInfo.Unit,
                SupplyCode = lockInfo.SupplyCode,
                OrderType = lockInfo.OrderType,
                CurrentBarcode = newBarcode,
                IsSplitted = 1,
                ParentLockId = lockInfo.Id,
                Operator = App.User.UserName,
                FactoryArea = lockInfo.FactoryArea,
                lineNo = lockInfo.lineNo,
                WarehouseCode = lockInfo.WarehouseCode,
                BarcodeQty = lockInfo.BarcodeQty,
                BarcodeUnit = lockInfo.BarcodeUnit,
            };
            await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
            // æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity = remainQty;
            lockInfo.OrderQuantity = remainQty;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // é‡è¦ï¼šæ‰‹åŠ¨æ‹†åŒ…ä¸æ”¹å˜è®¢å•æ˜Žç»†çš„æ€»åˆ†é…æ•°é‡
            // å› ä¸ºåˆ†é…æ•°é‡åªæ˜¯ä»Žä¸€ä¸ªé”å®šä¿¡æ¯è½¬ç§»åˆ°å¦ä¸€ä¸ªé”å®šä¿¡æ¯
            _logger.LogInformation($"手动拆包 - è®¢å•明细总分配数量保持不变");
            // è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, false);
            // åˆ›å»ºæ‹†åŒ…结果列表
            var splitResults = CreateSplitResults(lockInfo, splitQuantity, remainQty, newBarcode, stockDetail.Barcode);
            _logger.LogInformation($"手动拆包逻辑执行完成");
            return splitResults;
        }
        /// <summary>
        /// éªŒè¯æ‹†åŒ…后数据一致性
        /// </summary>
        private async Task ValidateDataConsistencyAfterSplit(long orderDetailId, decimal expectedAllocatedQty, decimal expectedLockQty)
        {
            // é‡æ–°èŽ·å–è®¢å•æ˜Žç»†æ•°æ®
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            if (orderDetail == null)
                return;
            // è®¡ç®—所有锁定信息的总分配数量
            var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId)
                .ToListAsync();
            decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
            _logger.LogInformation($"数据一致性验证 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQty}");
            // å¦‚果数据不一致,记录警告
            if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
            {
                _logger.LogWarning($"数据不一致 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQty}");
            }
            // éªŒè¯åˆ†é…æ•°é‡æ²¡æœ‰å¼‚常变化
            if (Math.Abs(orderDetail.AllocatedQuantity - expectedAllocatedQty) > 0.01m)
            {
                _logger.LogWarning($"分配数量异常变化 - æœŸæœ›: {expectedAllocatedQty}, å®žé™…: {orderDetail.AllocatedQuantity}");
            }
            if (Math.Abs(orderDetail.LockQuantity - expectedLockQty) > 0.01m)
            {
                _logger.LogWarning($"锁定数量异常变化 - æœŸæœ›: {expectedLockQty}, å®žé™…: {orderDetail.LockQuantity}");
            }
        }
        #endregion
        #region å–消拆包 
@@ -588,15 +868,45 @@
            Dt_OutStockLockInfo originalLockInfo, Dt_OutStockLockInfo newLockInfo,
            Dt_StockInfoDetail newStockDetail)
        {
            // 1. æ¢å¤åŽŸé”å®šä¿¡æ¯
            // æ³¨æ„ï¼šè¿™é‡Œéœ€è¦ç´¯åŠ ï¼Œè€Œä¸æ˜¯ç®€å•çš„èµ‹å€¼ï¼Œå› ä¸ºå¯èƒ½æœ‰å¤šæ¬¡æ‹†åŒ…
            originalLockInfo.AssignQuantity += splitRecord.SplitQty;
            originalLockInfo.OrderQuantity += splitRecord.SplitQty;
            _logger.LogInformation($"开始执行取消拆包逻辑 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}, æ‹†åŒ…数量: {splitRecord.SplitQty}");
            // èŽ·å–è®¢å•æ˜Žç»†
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == originalLockInfo.OrderDetailId);
            if (orderDetail == null)
                throw new InvalidOperationException("未找到订单明细");
            // è®°å½•取消拆包前的关键数据
            decimal originalOrderDetailAllocatedQty = orderDetail.AllocatedQuantity;
            decimal originalOrderDetailLockQty = orderDetail.LockQuantity;
            _logger.LogInformation($"取消拆包前数据 - è®¢å•明细分配数量: {originalOrderDetailAllocatedQty}, é”å®šæ•°é‡: {originalOrderDetailLockQty}");
            // æ¢å¤åŽŸé”å®šä¿¡æ¯
            decimal originalAssignQtyBefore = originalLockInfo.AssignQuantity;
            decimal originalOrderQtyBefore = originalLockInfo.OrderQuantity;
            // æ ¹æ®æ‹†åŒ…类型决定如何恢复
            if (splitRecord.IsAutoSplit)
            {
                // è‡ªåŠ¨æ‹†åŒ…ï¼šåŽŸé”å®šä¿¡æ¯ä¿æŒä¸å˜ï¼Œåªéœ€è¦åˆ é™¤æ–°é”å®šä¿¡æ¯
                _logger.LogInformation($"取消自动拆包 - åŽŸé”å®šä¿¡æ¯ä¿æŒä¸å˜");
            }
            else
            {
                // æ‰‹åŠ¨æ‹†åŒ…ï¼šæ¢å¤åŽŸé”å®šä¿¡æ¯çš„åˆ†é…æ•°é‡
                originalLockInfo.AssignQuantity += splitRecord.SplitQty;
                originalLockInfo.OrderQuantity += splitRecord.SplitQty;
                _logger.LogInformation($"取消手动拆包 - æ¢å¤åŽŸé”å®šä¿¡æ¯åˆ†é…æ•°é‡ä»Ž {originalAssignQtyBefore} å¢žåŠ åˆ° {originalLockInfo.AssignQuantity}");
            }
            // å¦‚果原锁定信息的状态是拣选完成,需要重新设置为出库中
            if (originalLockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
            {
                originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中;
                _logger.LogInformation($"原锁定信息状态从拣选完成恢复为出库中");
            }
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
@@ -605,43 +915,176 @@
            var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
            originalStock.StockQuantity += splitRecord.SplitQty;
            // å¦‚果原库存状态是出库完成,需要重新设置为出库锁定
            if (originalStock.Status == (int)StockStatusEmun.出库完成)
            if (originalStock != null)
            {
                originalStock.Status = (int)StockStatusEmun.出库锁定;
                if (splitRecord.IsAutoSplit)
                {
                    // è‡ªåŠ¨æ‹†åŒ…ï¼šåŽŸåº“å­˜æ˜Žç»†ä¿æŒä¸å˜
                    _logger.LogInformation($"取消自动拆包 - åŽŸåº“å­˜æ˜Žç»†ä¿æŒä¸å˜");
                }
                else
                {
                    // æ‰‹åŠ¨æ‹†åŒ…ï¼šæ¢å¤åŽŸåº“å­˜æ•°é‡
                    decimal originalStockQtyBefore = originalStock.StockQuantity;
                    originalStock.StockQuantity += splitRecord.SplitQty;
                    _logger.LogInformation($"取消手动拆包 - æ¢å¤åŽŸåº“å­˜æ˜Žç»†æ•°é‡ä»Ž {originalStockQtyBefore} å¢žåŠ åˆ° {originalStock.StockQuantity}");
                    // å¦‚果原库存状态是出库完成,需要重新设置为出库锁定
                    if (originalStock.Status == (int)StockStatusEmun.出库完成)
                    {
                        originalStock.Status = (int)StockStatusEmun.出库锁定;
                        _logger.LogInformation($"原库存状态从出库完成恢复为出库锁定");
                    }
                    await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
                }
            }
            await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
            // åˆ é™¤æ–°é”å®šä¿¡æ¯
            _logger.LogInformation($"删除新锁定信息 - æ¡ç : {newLockInfo.CurrentBarcode}, åˆ†é…æ•°é‡: {newLockInfo.AssignQuantity}");
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == newLockInfo.Id)
                .ExecuteCommandAsync();
            // åˆ é™¤æ–°åº“存明细
            _logger.LogInformation($"删除新库存明细 - æ¡ç : {newStockDetail.Barcode}, åº“存数量: {newStockDetail.StockQuantity}");
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == newLockInfo.CurrentBarcode)
                .ExecuteCommandAsync();
            //标记拆包记录为已撤销
            // å¦‚果是自动拆包,需要减少订单明细的分配数量和锁定数量
            if (splitRecord.IsAutoSplit)
            {
                decimal originalAllocatedBefore = orderDetail.AllocatedQuantity;
                decimal originalLockBefore = orderDetail.LockQuantity;
                orderDetail.AllocatedQuantity -= splitRecord.SplitQty;
                orderDetail.LockQuantity -= splitRecord.SplitQty;
                // è¾¹ç•Œæ£€æŸ¥ï¼šç¡®ä¿æ•°é‡ä¸ä¼šä¸ºè´Ÿæ•°
                if (orderDetail.AllocatedQuantity < 0)
                {
                    _logger.LogWarning($"分配数量出现负数,重置为0。原值: {orderDetail.AllocatedQuantity + splitRecord.SplitQty}, å‡å°‘: {splitRecord.SplitQty}");
                    orderDetail.AllocatedQuantity = 0;
                }
                if (orderDetail.LockQuantity < 0)
                {
                    _logger.LogWarning($"锁定数量出现负数,重置为0。原值: {orderDetail.LockQuantity + splitRecord.SplitQty}, å‡å°‘: {splitRecord.SplitQty}");
                    orderDetail.LockQuantity = 0;
                }
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                _logger.LogInformation($"取消自动拆包减少订单明细数量 - åˆ†é…æ•°é‡ä»Ž {originalAllocatedBefore} å‡å°‘到 {orderDetail.AllocatedQuantity}");
                _logger.LogInformation($"取消自动拆包减少订单明细数量 - é”å®šæ•°é‡ä»Ž {originalLockBefore} å‡å°‘到 {orderDetail.LockQuantity}");
            }
            // æ ‡è®°æ‹†åŒ…记录为已撤销
            splitRecord.IsReverted = true;
            splitRecord.RevertTime = DateTime.Now;
            splitRecord.RevertOperator = App.User.UserName;
            await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
            _logger.LogInformation($"标记拆包记录为已撤销");
            // éªŒè¯å–消拆包后数据一致性
            await ValidateDataConsistencyAfterCancelSplit(orderDetail.Id, originalOrderDetailAllocatedQty, originalOrderDetailLockQty, splitRecord.IsAutoSplit, splitRecord.SplitQty);
            // æ£€æŸ¥å¹¶æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•状态
            await CheckAndUpdateBatchStatus(originalLockInfo.BatchNo);
            await CheckAndUpdateOrderStatus(originalLockInfo.OrderNo);
        }
            _logger.LogInformation($"取消拆包逻辑执行完成");
        }
        /// <summary>
        /// éªŒè¯å–消拆包后数据一致性 - æœ€æ–°ç‰ˆæœ¬
        /// </summary>
        private async Task ValidateDataConsistencyAfterCancelSplit(long orderDetailId, decimal originalAllocatedQty, decimal originalLockQty, bool isAutoSplit, decimal splitQuantity)
        {
            // é‡æ–°èŽ·å–è®¢å•æ˜Žç»†æ•°æ®
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            if (orderDetail == null)
                return;
            // è®¡ç®—所有锁定信息的总分配数量
            var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId)
                .ToListAsync();
            decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
            _logger.LogInformation($"取消拆包后数据一致性验证 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQty}");
            // æ ¹æ®æ‹†åŒ…类型计算期望值
            decimal expectedAllocatedQty;
            decimal expectedLockQty;
            if (isAutoSplit)
            {
                // è‡ªåŠ¨æ‹†åŒ…å–æ¶ˆï¼šåˆ†é…æ•°é‡åº”è¯¥å‡å°‘æ‹†åŒ…æ•°é‡
                expectedAllocatedQty = originalAllocatedQty - splitQuantity;
                expectedLockQty = originalLockQty - splitQuantity;
                _logger.LogInformation($"取消自动拆包期望值 - åˆ†é…æ•°é‡: {expectedAllocatedQty}, é”å®šæ•°é‡: {expectedLockQty}");
            }
            else
            {
                // æ‰‹åŠ¨æ‹†åŒ…å–æ¶ˆï¼šåˆ†é…æ•°é‡åº”è¯¥ä¿æŒä¸å˜
                expectedAllocatedQty = originalAllocatedQty;
                expectedLockQty = originalLockQty;
                _logger.LogInformation($"取消手动拆包期望值 - åˆ†é…æ•°é‡: {expectedAllocatedQty}, é”å®šæ•°é‡: {expectedLockQty}");
            }
            // è¾¹ç•Œæ£€æŸ¥ï¼šç¡®ä¿æœŸæœ›å€¼ä¸ä¸ºè´Ÿæ•°
            if (expectedAllocatedQty < 0)
            {
                _logger.LogWarning($"期望分配数量为负数,重置为0。计算值: {expectedAllocatedQty}");
                expectedAllocatedQty = 0;
            }
            if (expectedLockQty < 0)
            {
                _logger.LogWarning($"期望锁定数量为负数,重置为0。计算值: {expectedLockQty}");
                expectedLockQty = 0;
            }
            // éªŒè¯åˆ†é…æ•°é‡
            if (Math.Abs(orderDetail.AllocatedQuantity - expectedAllocatedQty) > 0.01m)
            {
                _logger.LogWarning($"取消拆包后分配数量异常 - æœŸæœ›: {expectedAllocatedQty}, å®žé™…: {orderDetail.AllocatedQuantity}");
            }
            // éªŒè¯é”å®šæ•°é‡
            if (Math.Abs(orderDetail.LockQuantity - expectedLockQty) > 0.01m)
            {
                _logger.LogWarning($"取消拆包后锁定数量异常 - æœŸæœ›: {expectedLockQty}, å®žé™…: {orderDetail.LockQuantity}");
            }
            // éªŒè¯è®¢å•明细分配数量与锁定信息总分配数量的一致性
            if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
            {
                _logger.LogWarning($"取消拆包后数据不一致 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQty}");
            }
            // è®°å½•详细的一致性报告
            _logger.LogInformation($"取消拆包数据一致性报告 - " +
                                  $"订单明细分配数量: {orderDetail.AllocatedQuantity}, " +
                                  $"订单明细锁定数量: {orderDetail.LockQuantity}, " +
                                  $"锁定信息总分配数量: {totalLockAssignQty}, " +
                                  $"拆包类型: {(isAutoSplit ? "自动" : "手动")}, " +
                                  $"拆包数量: {splitQuantity}");
        }
        /// <summary>
        /// éªŒè¯å–消拆包请求 
        /// </summary>
        private async Task<ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateCancelSplitRequest(
            string orderNo, string palletCode, string newBarcode)
        {
            _logger.LogInformation($"开始验证取消拆包请求 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, æ¡ç : {newBarcode}");
            // æŸ¥æ‰¾æ‹†åŒ…记录
            var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(x => x.NewBarcode == newBarcode &&
@@ -651,6 +1094,8 @@
            if (splitRecord == null)
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到拆包记录");
            _logger.LogInformation($"找到拆包记录 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}, æ‹†åŒ…数量: {splitRecord.SplitQty}");
            // æŸ¥æ‰¾æ–°é”å®šä¿¡æ¯
            var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
@@ -662,13 +1107,35 @@
            if (newLockInfo == null)
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到新锁定信息");
            // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦å·²è¢«åˆ†æ‹£
            var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                .Where(x => x.Barcode == newBarcode && !x.IsCancelled)
                .FirstAsync();
            _logger.LogInformation($"找到新锁定信息 - çŠ¶æ€: {newLockInfo.Status}, å·²æ‹£é€‰: {newLockInfo.PickedQty}, åˆ†é…æ•°é‡: {newLockInfo.AssignQuantity}");
            if (pickingRecord != null)
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("该条码已被分拣,无法取消拆包");
            // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦å·²è¢«åˆ†æ‹£
            var newBarcodePickingRecords = await Db.Queryable<Dt_PickingRecord>()
                .Where(x => x.Barcode == newBarcode && x.OrderNo == orderNo && !x.IsCancelled)
                .ToListAsync();
            if (newBarcodePickingRecords.Any())
            {
                var totalPickedQty = newBarcodePickingRecords.Sum(x => x.PickQuantity);
                _logger.LogWarning($"新条码 {newBarcode} å·²è¢«åˆ†æ‹£ï¼Œæ€»æ‹£é€‰æ•°é‡: {totalPickedQty}");
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error(
                    $"新条码已被分拣(已拣选数量:{totalPickedQty}),请先取消分拣,然后再取消拆包");
            }
            // æ£€æŸ¥åŽŸæ¡ç æ˜¯å¦å·²è¢«åˆ†æ‹£
            var originalBarcodePickingRecords = await Db.Queryable<Dt_PickingRecord>()
                .Where(x => x.Barcode == splitRecord.OriginalBarcode && x.OrderNo == orderNo && !x.IsCancelled)
                .ToListAsync();
            if (originalBarcodePickingRecords.Any())
            {
                var totalPickedQty = originalBarcodePickingRecords.Sum(x => x.PickQuantity);
                _logger.LogWarning($"原条码 {splitRecord.OriginalBarcode} å·²è¢«åˆ†æ‹£ï¼Œæ€»æ‹£é€‰æ•°é‡: {totalPickedQty}");
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error(
                    $"原条码已被分拣(已拣选数量:{totalPickedQty}),请先取消分拣,然后再取消拆包");
            }
            _logger.LogInformation($"新旧条码均未被分拣,可以取消拆包");
            // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦è¢«å†æ¬¡æ‹†åŒ…
            var childSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
@@ -678,12 +1145,18 @@
            if (childSplitRecords.Any())
            {
                var childBarcodes = string.Join(", ", childSplitRecords.Select(x => x.NewBarcode));
                _logger.LogWarning($"条码 {newBarcode} å·²è¢«å†æ¬¡æ‹†åŒ…,生成的新条码: {childBarcodes}");
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error(
                    $"该条码已被再次拆包,生成的新条码:{childBarcodes},请先取消后续拆包");
            }
            var newStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == newBarcode);
            if (newStockDetail == null)
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到新库存明细");
            _logger.LogInformation($"取消拆包验证通过 - æ¡ç : {newBarcode}");
            return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((splitRecord, newLockInfo, newStockDetail));
        }
@@ -707,11 +1180,28 @@
                if (!splitChain.Any())
                    return WebResponseContent.Instance.Error("未找到拆包记录");
                // 2. æŒ‰æ‹†åŒ…顺序倒序取消(从最新的开始取消)
                _logger.LogInformation($"找到拆包链,共 {splitChain.Count} æ¡è®°å½•");
                // 2. æ”¶é›†æ‹†åŒ…链中涉及的所有条码(包括原条码和新条码)
                var allBarcodesInChain = new List<string> { startBarcode };
                allBarcodesInChain.AddRange(splitChain.Select(x => x.NewBarcode));
                // 3. æ£€æŸ¥æ‹†åŒ…链中是否有已被分拣的条码
                var pickedBarcodesInfo = await GetPickedBarcodesInfo(orderNo, allBarcodesInChain);
                if (pickedBarcodesInfo.Any())
                {
                    var pickedBarcodes = string.Join(", ", pickedBarcodesInfo.Select(x => $"{x.Barcode}(已拣选{x.PickedQty})"));
                    return WebResponseContent.Instance.Error(
                        $"以下条码已被分拣,请先取消分拣:{pickedBarcodes}");
                }
                // 4. æŒ‰æ‹†åŒ…顺序倒序取消(从最新的开始取消)
                var reversedChain = splitChain.OrderByDescending(x => x.SplitTime).ToList();
                foreach (var splitRecord in reversedChain)
                {
                    _logger.LogInformation($"取消拆包记录 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}");
                    await CancelSingleSplitPackage(splitRecord, palletCode);
                }
@@ -727,6 +1217,133 @@
            }
        }
        /// <summary>
        /// èŽ·å–æ¡ç çš„æ‹†åŒ…å’Œæ‹£é€‰çŠ¶æ€
        /// </summary>
        public async Task<WebResponseContent> GetBarcodeSplitAndPickStatus(string orderNo, string barcode)
        {
            try
            {
                // 1. èŽ·å–æ‹†åŒ…ä¿¡æ¯
                var splitChain = await GetSplitPackageChain(orderNo, barcode);
                var isOriginalBarcode = !splitChain.Any(x => x.NewBarcode == barcode);
                // 2. èŽ·å–æ‹£é€‰ä¿¡æ¯
                var pickingRecords = await Db.Queryable<Dt_PickingRecord>()
                    .Where(x => x.Barcode == barcode && x.OrderNo == orderNo && !x.IsCancelled)
                    .ToListAsync();
                var totalPickedQty = pickingRecords.Sum(x => x.PickQuantity);
                // 3. èŽ·å–é”å®šä¿¡æ¯
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == barcode && x.OrderNo == orderNo)
                    .FirstAsync();
                // 4. èŽ·å–åº“å­˜ä¿¡æ¯
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == barcode)
                    .FirstAsync();
                var statusInfo = new BarcodeStatusInfoDto
                {
                    Barcode = barcode,
                    OrderNo = orderNo,
                    IsOriginalBarcode = isOriginalBarcode,
                    SplitChainCount = splitChain.Count,
                    HasBeenPicked = pickingRecords.Any(),
                    TotalPickedQuantity = totalPickedQty,
                    PickRecordCount = pickingRecords.Count,
                    LockInfoStatus = lockInfo?.Status ?? 0,
                    LockInfoPickedQty = lockInfo?.PickedQty ?? 0,
                    LockInfoAssignQty = lockInfo?.AssignQuantity ?? 0,
                    StockQuantity = stockDetail?.StockQuantity ?? 0,
                    StockStatus = stockDetail?.Status ?? 0,
                    CanCancelSplit = !pickingRecords.Any(), // æœªè¢«åˆ†æ‹£æ‰èƒ½å–消拆包
                    NeedCancelPickFirst = pickingRecords.Any() // éœ€è¦å…ˆå–消分拣
                };
                // 5. èŽ·å–æ“ä½œå»ºè®®
                statusInfo.OperationSuggestions = GetOperationSuggestions(statusInfo);
                return WebResponseContent.Instance.OK("获取状态成功", statusInfo);
            }
            catch (Exception ex)
            {
                _logger.LogError($"获取条码状态失败 - OrderNo: {orderNo}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error("获取条码状态失败");
            }
        }
        /// <summary>
        /// èŽ·å–æ“ä½œå»ºè®®
        /// </summary>
        private List<string> GetOperationSuggestions(BarcodeStatusInfoDto statusInfo)
        {
            var suggestions = new List<string>();
            if (statusInfo.HasBeenPicked)
            {
                suggestions.Add($"该条码已被分拣(数量:{statusInfo.TotalPickedQuantity}),如需取消拆包,请先取消分拣");
                if (statusInfo.IsOriginalBarcode)
                {
                    suggestions.Add("这是原条码,取消分拣后将恢复为可分拣状态");
                }
                else
                {
                    suggestions.Add("这是拆包生成的新条码,取消分拣后才能取消拆包");
                }
            }
            else
            {
                if (statusInfo.IsOriginalBarcode && statusInfo.SplitChainCount > 0)
                {
                    suggestions.Add("这是原条码,可以取消拆包链");
                }
                else if (!statusInfo.IsOriginalBarcode)
                {
                    suggestions.Add("这是拆包生成的新条码,可以单独取消拆包");
                }
            }
            if (statusInfo.LockInfoStatus == (int)OutLockStockStatusEnum.拣选完成)
            {
                suggestions.Add("锁定状态:拣选完成");
            }
            else if (statusInfo.LockInfoStatus == (int)OutLockStockStatusEnum.出库中)
            {
                suggestions.Add("锁定状态:出库中");
            }
            return suggestions;
        }
        /// <summary>
        /// èŽ·å–å·²è¢«åˆ†æ‹£çš„æ¡ç ä¿¡æ¯
        /// </summary>
        private async Task<List<PickedBarcodeInfo>> GetPickedBarcodesInfo(string orderNo, List<string> barcodes)
        {
            var pickedBarcodes = new List<PickedBarcodeInfo>();
            foreach (var barcode in barcodes)
            {
                var pickingRecords = await Db.Queryable<Dt_PickingRecord>()
                    .Where(x => x.Barcode == barcode && x.OrderNo == orderNo && !x.IsCancelled)
                    .ToListAsync();
                if (pickingRecords.Any())
                {
                    var totalPickedQty = pickingRecords.Sum(x => x.PickQuantity);
                    pickedBarcodes.Add(new PickedBarcodeInfo
                    {
                        Barcode = barcode,
                        PickedQty = totalPickedQty,
                        PickRecordCount = pickingRecords.Count
                    });
                }
            }
            return pickedBarcodes;
        }
        /// <summary>
        /// èŽ·å–æ‹†åŒ…é“¾ - æŸ¥æ‰¾æŸä¸ªæ¡ç çš„æ‰€æœ‰æ‹†åŒ…记录(包括后续拆包)
        /// </summary>
@@ -771,6 +1388,27 @@
        /// </summary>
        private async Task CancelSingleSplitPackage(Dt_SplitPackageRecord splitRecord, string palletCode)
        {
            _logger.LogInformation($"开始取消单个拆包记录 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}");
            // å†æ¬¡éªŒè¯åˆ†æ‹£çŠ¶æ€ï¼ˆé˜²æ­¢å¹¶å‘æ“ä½œï¼‰
            var newBarcodePickingRecords = await Db.Queryable<Dt_PickingRecord>()
                .Where(x => x.Barcode == splitRecord.NewBarcode && !x.IsCancelled)
                .ToListAsync();
            if (newBarcodePickingRecords.Any())
            {
                throw new InvalidOperationException($"新条码 {splitRecord.NewBarcode} åœ¨éªŒè¯åŽè¢«åˆ†æ‹£ï¼Œæ— æ³•取消拆包");
            }
            var originalBarcodePickingRecords = await Db.Queryable<Dt_PickingRecord>()
                .Where(x => x.Barcode == splitRecord.OriginalBarcode && !x.IsCancelled)
                .ToListAsync();
            if (originalBarcodePickingRecords.Any())
            {
                throw new InvalidOperationException($"原条码 {splitRecord.OriginalBarcode} åœ¨éªŒè¯åŽè¢«åˆ†æ‹£ï¼Œæ— æ³•取消拆包");
            }
            // æŸ¥æ‰¾ç›¸å…³æ•°æ®
            var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.CurrentBarcode == splitRecord.NewBarcode &&
@@ -785,6 +1423,8 @@
            // æ‰§è¡Œå–消逻辑
            await ExecuteCancelSplitLogic(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
            _logger.LogInformation($"取消单个拆包记录完成 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}");
        }
        #endregion
@@ -810,7 +1450,8 @@
                        NewBarcode = x.NewBarcode,
                        SplitQuantity = x.SplitQty,
                        Operator = x.Operator,
                        IsReverted = x.IsReverted
                        IsReverted = x.IsReverted,
                        IsAutoSplit = x.IsAutoSplit
                    }).ToList()
                };
@@ -899,68 +1540,1292 @@
        #endregion
        #region ç»Ÿä¸€å›žåº“逻辑
        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<PalletStatusAnalysis> AnalyzePalletStatus(string orderNo, string palletCode, int stockId)
        {
            var result = new PalletStatusAnalysis
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                StockId = stockId
            };
        #region åˆ†æ‰¹å›žåº“
            // åˆ†æžæœªåˆ†æ‹£çš„出库锁定记录
            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);
                _logger.LogInformation($"发现{remainingLocks.Count}条未分拣锁定记录,总数量: {result.RemainingLocksReturnQty}");
            }
            // åˆ†æžæ‰˜ç›˜ä¸Šçš„库存货物
            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);
                _logger.LogInformation($"发现{palletStockGoods.Count}个库存货物,总数量: {result.PalletStockReturnQty}");
                // è®°å½•详细状态分布
                var statusGroups = palletStockGoods.GroupBy(x => x.Status);
                foreach (var group in statusGroups)
                {
                    _logger.LogInformation($"库存状态{group.Key}: {group.Count()}个货物,数量: {group.Sum(x => x.StockQuantity)}");
                }
            }
            //分析拆包记录
            var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
                           !it.IsReverted && it.Status != (int)SplitPackageStatusEnum.已拣选 &&
                           it.Status != (int)SplitPackageStatusEnum.已回库)
                .ToListAsync();
            if (splitRecords.Any())
            {
                result.HasSplitRecords = true;
                result.SplitRecords = splitRecords;
                result.SplitReturnQty = await CalculateSplitReturnQuantity(splitRecords, stockId);
                _logger.LogInformation($"发现{splitRecords.Count}条未拣选拆包记录,总数量: {result.SplitReturnQty}");
            }
            // 4. è®¡ç®—总回库数量和空托盘状态
            result.TotalReturnQty = result.RemainingLocksReturnQty + result.PalletStockReturnQty + result.SplitReturnQty;
            result.HasItemsToReturn = result.TotalReturnQty > 0;
            result.IsEmptyPallet = !result.HasItemsToReturn;
            // 5. æ£€æŸ¥æ˜¯å¦æœ‰è¿›è¡Œä¸­çš„任务
            result.HasActiveTasks = await _taskRepository.Db.Queryable<Dt_Task>()
                .Where(x => x.OrderNo == orderNo && x.TaskType == TaskTypeEnum.InPick.ObjToInt() &&
                           x.PalletCode == palletCode &&
                           x.TaskStatus == (int)TaskStatusEnum.New)
                .AnyAsync();
            _logger.LogInformation($"托盘状态分析完成 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, " +
                                  $"总回库数量: {result.TotalReturnQty}, æ˜¯å¦ç©ºæ‰˜ç›˜: {result.IsEmptyPallet}, " +
                                  $"有进行中任务: {result.HasActiveTasks}");
            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 (splitRecord.Status != (int)SplitPackageStatusEnum.已撤销)
                    continue;
                // æ£€æŸ¥åŽŸæ¡ç 
                if (!processedBarcodes.Contains(splitRecord.OriginalBarcode))
                {
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockId &&
                           it.Status != StockStatusEmun.出库完成.ObjToInt())
                        .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 && it.Status != StockStatusEmun.出库完成.ObjToInt())
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        totalQty += newStock.StockQuantity;
                        processedBarcodes.Add(splitRecord.NewBarcode);
                    }
                }
            }
            return totalQty;
        }
        /// <summary>
        /// åˆ†æ‰¹å›žåº“ - é‡Šæ”¾æœªæ‹£é€‰çš„库存
        /// ç»Ÿä¸€å›žåº“方法 - å¤„理托盘上所有剩余货物
        /// </summary>
        public async Task<WebResponseContent> BatchReturnStock(string orderNo, string palletCode)
        public async Task<WebResponseContent> ExecutePalletReturn(string orderNo, string palletCode, string returnReason = "分批回库")
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾æ‰˜ç›˜ä¸Šæœªå®Œæˆçš„锁定记录(只处理出库中的记录)
                var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.PalletCode == palletCode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
                if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
                    return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
                if (!unfinishedLocks.Any())
                    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 batchGroups = unfinishedLocks.GroupBy(x => x.OutboundBatchNo); // ä½¿ç”¨ OutboundBatchNo
                // åˆ†æžæ‰˜ç›˜çŠ¶æ€
                var statusAnalysis = await AnalyzePalletStatusForReturn(orderNo, palletCode, stockInfo.Id);
                foreach (var batchGroup in batchGroups)
                {
                    var outboundBatchNo = batchGroup.Key;
                    var batchLocks = batchGroup.ToList();
                if (!statusAnalysis.HasItemsToReturn)
                    return await HandleEmptyPalletReturn(orderNo, palletCode, stockInfo);
                    // é‡Šæ”¾åº“存和锁定记录
                    foreach (var lockInfo in batchLocks)
                    {
                        await ReleaseLockAndStock(lockInfo);
                    }
                _logger.LogInformation($"开始回库操作 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, å›žåº“数量: {statusAnalysis.TotalReturnQty}");
                    // æ›´æ–°æ‰¹æ¬¡çŠ¶æ€
                    await UpdateBatchStatusForReturn(outboundBatchNo, batchLocks);
                // æ‰§è¡Œå›žåº“数据操作
                await ExecuteReturnDataOperations(statusAnalysis);
                    // æ›´æ–°è®¢å•明细的已分配数量
                    await UpdateOrderDetailAfterReturn(batchLocks);
                }
                // æ›´æ–°è®¢å•状态
                await UpdateOrderStatusAfterReturn(orderNo);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("分批回库成功");
                // åˆ›å»ºå›žåº“任务(AGV)
                await CreateReturnTask(orderNo, palletCode, stockInfo);
                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{statusAnalysis.TotalReturnQty}", new
                {
                    ReturnQuantity = statusAnalysis.TotalReturnQty,
                    ReturnBarcodes = statusAnalysis.AllBarcodes,
                    Reason = returnReason,
                    PalletCode = palletCode,
                    OrderNo = orderNo
                });
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"分批回库失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"分批回库失败:{ex.Message}");
                _logger.LogError($"回库操作失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
            }
        }
        /// <summary>
        /// æ‰§è¡Œå›žåº“数据操作
        /// ç¡®ä¿ä¸ä¼šå°†ç”Ÿæˆçš„æ¡ç æ•°é‡é”™è¯¯ç»‘定到锁定数量
        /// </summary>
        private async Task ExecuteReturnDataOperations(PalletStatusAnalysis statusAnalysis)
        {
            _logger.LogInformation($"开始执行回库数据操作 - è®¢å•: {statusAnalysis.OrderNo}, æ‰˜ç›˜: {statusAnalysis.PalletCode}");
            try
            {
                // 1. å¤„理已分配的未分拣锁定记录
                if (statusAnalysis.HasRemainingLocks)
                {
                    _logger.LogInformation($"处理 {statusAnalysis.RemainingLocks.Count} æ¡å·²åˆ†é…æœªåˆ†æ‹£é”å®šè®°å½•");
                    await HandleAllocatedLocksReturn(statusAnalysis.RemainingLocks);
                }
                // 2. å¤„理未分配的锁定记录(如自动拆包产生的)
                if (statusAnalysis.HasUnallocatedLocks)
                {
                    _logger.LogInformation($"处理 {statusAnalysis.UnallocatedLocks.Count} æ¡æœªåˆ†é…é”å®šè®°å½•");
                    await HandleUnallocatedLocksReturn(statusAnalysis.UnallocatedLocks);
                }
                // 3. å¤„理未分配的库存货物
                if (statusAnalysis.HasPalletStockGoods)
                {
                    _logger.LogInformation($"处理 {statusAnalysis.PalletStockGoods.Count} ä¸ªæœªåˆ†é…åº“存货物");
                    await HandleUnallocatedStockReturn(statusAnalysis.PalletStockGoods);
                }
                _logger.LogInformation($"回库数据操作完成 - æ€»å›žåº“数量: {statusAnalysis.TotalReturnQty}");
            }
            catch (Exception ex)
            {
                _logger.LogError($"回库数据操作失败 - è®¢å•: {statusAnalysis.OrderNo}, æ‰˜ç›˜: {statusAnalysis.PalletCode}, Error: {ex.Message}");
                throw;
            }
        }
        // <summary>
        /// å¤„理未分配的锁定记录回库
        /// ä¸éœ€è¦å‡å°‘订单明细的分配数量
        /// </summary>
        private async Task HandleUnallocatedLocksReturn(List<Dt_OutStockLockInfo> unallocatedLocks)
        {
            _logger.LogInformation($"开始处理未分配锁定记录回库 - å…± {unallocatedLocks.Count} æ¡è®°å½•");
            foreach (var lockInfo in unallocatedLocks)
            {
                // è®¡ç®—回库数量(未拣选的部分)
                decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                if (returnQty <= 0)
                {
                    _logger.LogInformation($"跳过未分配锁定记录 - é”å®šID: {lockInfo.Id}, å·²æ‹£é€‰å®Œæˆæˆ–无需回库");
                    continue;
                }
                _logger.LogInformation($"处理未分配锁定记录回库 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, å›žåº“数量: {returnQty}");
                // æ¢å¤åº“存状态
                await RestoreStockForLockInfo(lockInfo, returnQty);
                // æ›´æ–°é”å®šè®°å½•状态为已回库
                lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                lockInfo.Operator = App.User.UserName;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                _logger.LogInformation($"更新未分配锁定状态 - é”å®šID: {lockInfo.Id}, çŠ¶æ€: å‡ºåº“中 -> å·²å›žåº“");
                // é‡è¦ï¼šæœªåˆ†é…é”å®šè®°å½•不需要减少订单明细的分配数量
                _logger.LogInformation($"未分配锁定记录回库完成 - é”å®šID: {lockInfo.Id}, å›žåº“数量: {returnQty}, æ— éœ€æ›´æ–°è®¢å•明细");
            }
            _logger.LogInformation($"未分配锁定记录回库处理完成 - å…±å¤„理 {unallocatedLocks.Count} æ¡è®°å½•");
        }
        private async Task HandleAllocatedLocksReturn(List<Dt_OutStockLockInfo> allocatedLocks)
        {
            _logger.LogInformation($"开始处理已分配锁定记录回库 - å…± {allocatedLocks.Count} æ¡è®°å½•");
            // æŒ‰è®¢å•明细分组处理
            var orderDetailGroups = allocatedLocks.GroupBy(x => x.OrderDetailId);
            foreach (var group in orderDetailGroups)
            {
                var orderDetailId = group.Key;
                var groupLocks = group.ToList();
                _logger.LogInformation($"处理订单明细 {orderDetailId} çš„ {groupLocks.Count} æ¡é”å®šè®°å½•");
                // èŽ·å–è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == orderDetailId);
                if (orderDetail == null)
                {
                    _logger.LogWarning($"未找到订单明细 - OrderDetailId: {orderDetailId}");
                    continue;
                }
                decimal totalReturnQtyForDetail = 0;
                foreach (var lockInfo in groupLocks)
                {
                    // è®¡ç®—回库数量(未拣选的部分)
                    decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                    if (returnQty <= 0)
                    {
                        _logger.LogInformation($"跳过锁定记录 - é”å®šID: {lockInfo.Id}, å·²æ‹£é€‰å®Œæˆæˆ–无需回库");
                        continue;
                    }
                    _logger.LogInformation($"处理已分配锁定记录回库 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, å›žåº“数量: {returnQty}");
                    // æ¢å¤åº“存状态
                    await RestoreStockForLockInfo(lockInfo, returnQty);
                    // æ›´æ–°é”å®šè®°å½•状态为已回库
                    lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                    lockInfo.Operator = App.User.UserName;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    _logger.LogInformation($"更新已分配锁定状态 - é”å®šID: {lockInfo.Id}, çŠ¶æ€: å‡ºåº“中 -> å·²å›žåº“");
                    totalReturnQtyForDetail += returnQty;
                }
                // å‡å°‘订单明细的分配数量
                if (totalReturnQtyForDetail > 0)
                {
                    await ReduceOrderDetailAllocation(orderDetail, totalReturnQtyForDetail);
                }
            }
            _logger.LogInformation($"已分配锁定记录回库处理完成 - å…±å¤„理 {allocatedLocks.Count} æ¡è®°å½•");
        }
        /// <summary>
        /// æ¢å¤é”å®šè®°å½•对应的库存
        /// </summary>
        private async Task RestoreStockForLockInfo(Dt_OutStockLockInfo lockInfo, decimal returnQty)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
            if (stockDetail != null)
            {
                // è®°å½•恢复前的库存状态
                decimal originalStockQty = stockDetail.StockQuantity;
                decimal originalOutboundQty = stockDetail.OutboundQuantity;
                // æ¢å¤åº“存数量:出库数量减少,库存数量增加
                stockDetail.OutboundQuantity -= returnQty;
                stockDetail.StockQuantity += returnQty;
                // ç¡®ä¿æ•°é‡ä¸ä¼šä¸ºè´Ÿæ•°
                if (stockDetail.OutboundQuantity < 0)
                {
                    _logger.LogWarning($"出库数量出现负数,重置为0。原值: {stockDetail.OutboundQuantity + returnQty}");
                    stockDetail.OutboundQuantity = 0;
                }
                // æ¢å¤åº“存状态为可用状态
                if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
                {
                    stockDetail.Status = (int)StockStatusEmun.入库完成;
                    _logger.LogInformation($"库存状态更新为入库完成 - æ¡ç : {stockDetail.Barcode}");
                }
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"恢复库存状态 - æ¡ç : {stockDetail.Barcode}, " +
                                     $"库存数量: {originalStockQty} -> {stockDetail.StockQuantity}, " +
                                     $"出库数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
            }
            else
            {
                _logger.LogWarning($"未找到对应的库存信息 - æ¡ç : {lockInfo.CurrentBarcode}, StockId: {lockInfo.StockId}");
            }
        }
        /// <summary>
        /// åˆ›å»ºå›žåº“任务
        /// </summary>
        private async Task CreateReturnTask(string orderNo, string palletCode, Dt_StockInfo stockInfo)
        {
            // èŽ·å–å½“å‰ä»»åŠ¡ä¿¡æ¯
            var currentTask = await _taskRepository.Db.Queryable<Dt_Task>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .FirstAsync();
            if (currentTask != null)
            {
                // åˆ†é…æ–°è´§ä½
                var newLocation = _locationInfoService.AssignLocation(stockInfo.LocationType);
                var returnTask = new Dt_Task()
                {
                    CurrentAddress = stations[currentTask.TargetAddress],
                    Grade = 0,
                    PalletCode = palletCode,
                    NextAddress = "",
                    OrderNo = orderNo,
                    Roadway = newLocation.RoadwayNo,
                    SourceAddress = stations[currentTask.TargetAddress],
                    TargetAddress = newLocation.LocationCode,
                    TaskStatus = TaskStatusEnum.New.ObjToInt(),
                    TaskType = TaskTypeEnum.InPick.ObjToInt(),
                    PalletType = stockInfo.PalletType,
                    WarehouseId = currentTask.WarehouseId
                };
                try
                {
                    await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
                }
                catch (Exception ex)
                {
                    _logger.LogInformation($"创建回库任务失败 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                    throw new Exception($"创建回库任务失败 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                }
                // å‘送ESS命令
                await SendESSCommands(palletCode, currentTask.TargetAddress, returnTask);
                _logger.LogInformation($"创建回库任务成功 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
            }
        }
        /// <summary>
        /// æ›´æ–°å›žåº“后的订单状态
        /// </summary>
        private async Task UpdateOrderStatusAfterReturn(string orderNo)
        {
            // æ£€æŸ¥è®¢å•是否还有未完成的锁定记录
            var activeLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           (x.Status == (int)OutLockStockStatusEnum.出库中 ||
                            x.Status == (int)OutLockStockStatusEnum.回库中))
                .ToListAsync();
            if (!activeLocks.Any())
            {
                // æ‰€æœ‰é”å®šè®°å½•都已完成或已回库,更新订单状态
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => new Dt_OutboundOrder
                    {
                        OrderStatus = (int)OutOrderStatusEnum.出库完成,
                    })
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
                _logger.LogInformation($"更新订单状态为出库完成 - è®¢å•: {orderNo}");
            }
        }
        /// <summary>
        /// å¤„理空托盘回库
        /// </summary>
        private async Task<WebResponseContent> HandleEmptyPalletReturn(string orderNo, string palletCode, Dt_StockInfo stockInfo)
        {
            _logger.LogInformation($"处理空托盘回库 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
            try
            {
                // æ¸…理零库存数据
                await CleanupZeroStockData(stockInfo.Id);
                // åˆ›å»ºç©ºæ‰˜ç›˜åº“存记录
                var emptyStockInfo = new Dt_StockInfo()
                {
                    PalletType = PalletTypeEnum.Empty.ObjToInt(),
                    StockStatus = StockStatusEmun.组盘暂存.ObjToInt(),
                    PalletCode = palletCode,
                    LocationType = stockInfo.LocationType
                };
                emptyStockInfo.Details = new List<Dt_StockInfoDetail>();
                _stockInfoService.AddMaterielGroup(emptyStockInfo);
                // åˆ›å»ºç©ºæ‰˜ç›˜å›žåº“任务
                await CreateReturnTask(orderNo, palletCode, emptyStockInfo);
                return WebResponseContent.Instance.OK("空托盘回库成功");
            }
            catch (Exception ex)
            {
                _logger.LogError($"空托盘回库失败: {ex.Message}");
                return WebResponseContent.Instance.Error($"空托盘回库失败: {ex.Message}");
            }
        }
        /// <summary>
        /// åˆ†æžæ‰˜ç›˜çŠ¶æ€ç”¨äºŽå›žåº“
        /// ç¡®ä¿ä¸ä¼šé”™è¯¯è¯†åˆ«éœ€è¦å›žåº“的物品
        /// </summary>
        private async Task<PalletStatusAnalysis> AnalyzePalletStatusForReturn(string orderNo, string palletCode, int stockId)
        {
            var result = new PalletStatusAnalysis
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                StockId = stockId
            };
            // 1. åˆ†æžæœªåˆ†æ‹£çš„锁定记录(状态为出库中)
            var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == (int)OutLockStockStatusEnum.出库中)
                .ToListAsync();
            if (unfinishedLocks.Any())
            {
                //  åŒºåˆ†å·²åˆ†é…å’Œæœªåˆ†é…çš„锁定记录
                var allocatedLocks = unfinishedLocks.Where(x => x.IsUnallocated != 1 && x.OrderDetailId > 0).ToList();
                var unallocatedLocks = unfinishedLocks.Where(x => x.IsUnallocated == 1 || x.OrderDetailId == 0).ToList();
                // å¤„理已分配的锁定记录
                if (allocatedLocks.Any())
                {
                    result.HasRemainingLocks = true;
                    result.RemainingLocks = allocatedLocks;
                    result.RemainingLocksReturnQty = allocatedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                    foreach (var lockInfo in allocatedLocks)
                    {
                        if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
                        {
                            result.AllBarcodes.Add(lockInfo.CurrentBarcode);
                        }
                    }
                    _logger.LogInformation($"发现{allocatedLocks.Count}条已分配未分拣锁定记录,总数量: {result.RemainingLocksReturnQty}");
                }
                // å¤„理未分配的锁定记录(如自动拆包产生的)
                if (unallocatedLocks.Any())
                {
                    result.HasUnallocatedLocks = true;
                    result.UnallocatedLocks = unallocatedLocks;
                    result.UnallocatedLocksReturnQty = unallocatedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                    foreach (var lockInfo in unallocatedLocks)
                    {
                        if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
                        {
                            result.AllBarcodes.Add(lockInfo.CurrentBarcode);
                        }
                    }
                    _logger.LogInformation($"发现{unallocatedLocks.Count}条未分配锁定记录,总数量: {result.UnallocatedLocksReturnQty}");
                }
            }
            // 2. åˆ†æžæ‰˜ç›˜ä¸Šçš„剩余库存货物(状态为出库锁定但未分配)
            var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.StockId == stockId &&
                           x.Status == (int)StockStatusEmun.出库锁定 &&
                           x.StockQuantity > 0)
                .ToListAsync();
            // è¿‡æ»¤æŽ‰å·²ç»è¢«é”å®šè®°å½•占用的库存
            var lockedBarcodes = unfinishedLocks.Select(x => x.CurrentBarcode).ToList();
            var unlockedStockGoods = palletStockGoods.Where(x => !lockedBarcodes.Contains(x.Barcode)).ToList();
            // è¿›ä¸€æ­¥è¿‡æ»¤ï¼šæ£€æŸ¥è¿™äº›åº“存是否有关联的锁定记录
            var trulyUnallocatedGoods = new List<Dt_StockInfoDetail>();
            foreach (var stock in unlockedStockGoods)
            {
                var hasLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == stock.Barcode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .AnyAsync();
                if (!hasLock)
                {
                    trulyUnallocatedGoods.Add(stock);
                }
            }
            if (trulyUnallocatedGoods.Any())
            {
                result.HasPalletStockGoods = true;
                result.PalletStockGoods = trulyUnallocatedGoods;
                result.PalletStockReturnQty = trulyUnallocatedGoods.Sum(x => x.StockQuantity);
                foreach (var stock in trulyUnallocatedGoods)
                {
                    result.AllBarcodes.Add(stock.Barcode);
                }
                _logger.LogInformation($"发现{trulyUnallocatedGoods.Count}个真正未分配库存货物,总数量: {result.PalletStockReturnQty}");
            }
            // 3. è®¡ç®—总回库数量
            result.TotalReturnQty = result.RemainingLocksReturnQty + result.UnallocatedLocksReturnQty + result.PalletStockReturnQty;
            result.HasItemsToReturn = result.TotalReturnQty > 0;
            result.IsEmptyPallet = !result.HasItemsToReturn;
            _logger.LogInformation($"托盘状态分析完成 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, æ€»å›žåº“数量: {result.TotalReturnQty}");
            return result;
        }
        /// <summary>
        /// å¤„理未分拣的锁定记录回库
        /// ç¡®ä¿ä¸ä¼šé”™è¯¯ç»‘定条码数量到锁定数量
        /// </summary>
        private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks)
        {
            _logger.LogInformation($"开始处理未分拣锁定记录回库 - å…± {remainingLocks.Count} æ¡è®°å½•");
            // æŒ‰è®¢å•明细分组处理,确保订单明细数据的一致性
            var orderDetailGroups = remainingLocks.GroupBy(x => x.OrderDetailId);
            foreach (var group in orderDetailGroups)
            {
                var orderDetailId = group.Key;
                var groupLocks = group.ToList();
                _logger.LogInformation($"处理订单明细 {orderDetailId} çš„ {groupLocks.Count} æ¡é”å®šè®°å½•");
                // èŽ·å–è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == orderDetailId);
                if (orderDetail == null)
                {
                    _logger.LogWarning($"未找到订单明细 - OrderDetailId: {orderDetailId}");
                    continue;
                }
                decimal totalReturnQtyForDetail = 0;
                foreach (var lockInfo in groupLocks)
                {
                    // åªå¤„理状态为出库中的锁定记录
                    if (lockInfo.Status != (int)OutLockStockStatusEnum.出库中)
                    {
                        _logger.LogInformation($"跳过非出库中状态的锁定记录 - é”å®šID: {lockInfo.Id}, çŠ¶æ€: {lockInfo.Status}");
                        continue;
                    }
                    // è®¡ç®—回库数量(未拣选的部分)
                    decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                    if (returnQty <= 0)
                    {
                        _logger.LogInformation($"跳过锁定记录 - é”å®šID: {lockInfo.Id}, å·²æ‹£é€‰å®Œæˆæˆ–无需回库");
                        continue;
                    }
                    _logger.LogInformation($"处理锁定记录回库 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, å›žåº“数量: {returnQty}");
                    // æ¢å¤åº“存状态
                    var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
                    if (stockDetail != null)
                    {
                        // è®°å½•恢复前的库存状态
                        decimal originalStockQty = stockDetail.StockQuantity;
                        decimal originalOutboundQty = stockDetail.OutboundQuantity;
                        // åªæ¢å¤å®žé™…的库存数量,不创建新的条码或绑定
                        // æ¢å¤åº“存数量:出库数量减少,库存数量增加
                        stockDetail.OutboundQuantity -= returnQty;
                        stockDetail.StockQuantity += returnQty;
                        // ç¡®ä¿æ•°é‡ä¸ä¼šä¸ºè´Ÿæ•°
                        if (stockDetail.OutboundQuantity < 0)
                        {
                            _logger.LogWarning($"出库数量出现负数,重置为0。原值: {stockDetail.OutboundQuantity + returnQty}");
                            stockDetail.OutboundQuantity = 0;
                        }
                        // æ¢å¤åº“存状态为可用状态
                        if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
                        {
                            stockDetail.Status = (int)StockStatusEmun.入库完成;
                            _logger.LogInformation($"库存状态更新为入库完成 - æ¡ç : {stockDetail.Barcode}");
                        }
                        await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                        _logger.LogInformation($"恢复库存状态 - æ¡ç : {stockDetail.Barcode}, " +
                                             $"库存数量: {originalStockQty} -> {stockDetail.StockQuantity}, " +
                                             $"出库数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
                    }
                    else
                    {
                        _logger.LogWarning($"未找到对应的库存信息 - æ¡ç : {lockInfo.CurrentBarcode}, StockId: {lockInfo.StockId}");
                        // é‡è¦ï¼šå¦‚果找不到库存信息,跳过此锁定记录,避免数据不一致
                        continue;
                    }
                    // æ›´æ–°é”å®šè®°å½•状态为已回库,但不修改分配数量
                    // åˆ†é…æ•°é‡åœ¨è®¢å•明细层面统一处理
                    var originalStatus = lockInfo.Status;
                    lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                    lockInfo.Operator = App.User.UserName;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    _logger.LogInformation($"更新锁定状态 - é”å®šID: {lockInfo.Id}, çŠ¶æ€: {originalStatus} -> {lockInfo.Status}");
                    totalReturnQtyForDetail += returnQty;
                    _logger.LogInformation($"锁定记录回库完成 - é”å®šID: {lockInfo.Id}, å›žåº“数量: {returnQty}");
                }
                // å‡å°‘订单明细的分配数量
                if (totalReturnQtyForDetail > 0)
                {
                    await ReduceOrderDetailAllocation(orderDetail, totalReturnQtyForDetail);
                }
            }
            _logger.LogInformation($"未分拣锁定记录回库处理完成 - å…±å¤„理 {remainingLocks.Count} æ¡è®°å½•");
        }
        /// <summary>
        /// å¤„理未分配的库存货物回库
        /// ç¡®ä¿ä¸ä¼šåˆ›å»ºæ–°çš„锁定记录
        /// </summary>
        private async Task HandleUnallocatedStockReturn(List<Dt_StockInfoDetail> stockGoods)
        {
            _logger.LogInformation($"开始处理未分配库存回库 - å…± {stockGoods.Count} ä¸ªè´§ç‰©");
            foreach (var stockDetail in stockGoods)
            {
                if (stockDetail.StockQuantity <= 0)
                {
                    _logger.LogInformation($"跳过零库存货物 - æ¡ç : {stockDetail.Barcode}");
                    continue;
                }
                _logger.LogInformation($"处理未分配库存回库 - æ¡ç : {stockDetail.Barcode}, æ•°é‡: {stockDetail.StockQuantity}");
                // æ£€æŸ¥æ˜¯å¦å·²ç»æœ‰å¯¹åº”的锁定记录
                var existingLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == stockDetail.Barcode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .FirstAsync();
                if (existingLock != null)
                {
                    _logger.LogWarning($"库存条码 {stockDetail.Barcode} å·²æœ‰é”å®šè®°å½•,跳过直接回库处理");
                    continue;
                }
                // è®°å½•恢复前的状态
                var originalStatus = stockDetail.Status;
                // ç›´æŽ¥æ¢å¤åº“存状态为可用状态,不创建任何锁定记录
                stockDetail.Status = (int)StockStatusEmun.入库完成;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"未分配库存回库完成 - æ¡ç : {stockDetail.Barcode}, çŠ¶æ€: {originalStatus} -> {stockDetail.Status}");
            }
            _logger.LogInformation($"未分配库存回库处理完成 - å…±å¤„理 {stockGoods.Count} ä¸ªè´§ç‰©");
        }
        /// <summary>
        /// å‡å°‘订单明细的分配数量
        /// ç¡®ä¿åˆ†é…æ•°é‡çš„减少是准确的
        /// </summary>
        private async Task ReduceOrderDetailAllocation(Dt_OutboundOrderDetail orderDetail, decimal reduceQty)
        {
            if (orderDetail == null)
                return;
            decimal originalAllocated = orderDetail.AllocatedQuantity;
            decimal originalLock = orderDetail.LockQuantity;
            // éªŒè¯å‡å°‘数量不会导致负数
            if (orderDetail.AllocatedQuantity < reduceQty)
            {
                _logger.LogWarning($"分配数量不足,调整减少数量 - åŽŸè®¡åˆ’å‡å°‘: {reduceQty}, å®žé™…可用: {orderDetail.AllocatedQuantity}");
                reduceQty = orderDetail.AllocatedQuantity;
            }
            // å‡å°‘分配数量和锁定数量
            orderDetail.AllocatedQuantity -= reduceQty;
            orderDetail.LockQuantity -= reduceQty;
            // ç¡®ä¿æ•°é‡ä¸ä¼šä¸ºè´Ÿæ•°
            if (orderDetail.AllocatedQuantity < 0)
            {
                _logger.LogWarning($"分配数量出现负数,重置为0。原值: {orderDetail.AllocatedQuantity + reduceQty}, å‡å°‘: {reduceQty}");
                orderDetail.AllocatedQuantity = 0;
            }
            if (orderDetail.LockQuantity < 0)
            {
                _logger.LogWarning($"锁定数量出现负数,重置为0。原值: {orderDetail.LockQuantity + reduceQty}, å‡å°‘: {reduceQty}");
                orderDetail.LockQuantity = 0;
            }
            // æ›´æ–°æ‰¹æ¬¡åˆ†é…çŠ¶æ€
            await UpdateBatchAllocateStatus(orderDetail);
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            _logger.LogInformation($"减少订单明细分配 - OrderDetailId: {orderDetail.Id}, " +
                                 $"分配数量: {originalAllocated} -> {orderDetail.AllocatedQuantity}, " +
                                 $"锁定数量: {originalLock} -> {orderDetail.LockQuantity}, " +
                                 $"减少数量: {reduceQty}");
            // éªŒè¯æ•°æ®ä¸€è‡´æ€§
            await ValidateOrderDetailConsistency(orderDetail.Id);
        }
        /// <summary>
        /// éªŒè¯è®¢å•明细数据一致性
        /// </summary>
        private async Task ValidateOrderDetailConsistency(long orderDetailId)
        {
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            if (orderDetail == null)
                return;
            // è®¡ç®—所有相关锁定记录的总分配数量
            var relatedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId &&
                           x.Status != (int)OutLockStockStatusEnum.已回库)
                .ToListAsync();
            decimal totalLockAssignQty = relatedLocks.Sum(x => x.AssignQuantity);
            // éªŒè¯è®¢å•明细分配数量与锁定记录总分配数量的一致性
            if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
            {
                _logger.LogWarning($"数据不一致警告 - OrderDetailId: {orderDetailId}, " +
                                  $"订单明细分配数量: {orderDetail.AllocatedQuantity}, " +
                                  $"锁定记录总分配数量: {totalLockAssignQty}");
            }
            else
            {
                _logger.LogInformation($"数据一致性验证通过 - OrderDetailId: {orderDetailId}");
            }
        }
        /// <summary>
        /// åˆ†æ‰¹å›žåº“ - è°ƒç”¨ç»Ÿä¸€å›žåº“方法
        /// </summary>
        public async Task<WebResponseContent> BatchReturnStock(string orderNo, string palletCode)
        {
            return await ExecutePalletReturn(orderNo, palletCode, "分批回库");
        }
        /// <summary>
        /// å‰©ä½™å›žåº“ - è°ƒç”¨ç»Ÿä¸€å›žåº“方法
        /// </summary>
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        {
            return await ExecutePalletReturn(orderNo, palletCode, reason);
        }
        /// <summary>
        /// å–走空箱 - å…ˆæ‰§è¡Œå›žåº“再清理 - å¢žå¼ºç‰ˆæœ¬
        /// </summary>
        public async Task<WebResponseContent> RemoveEmptyPallet(string orderNo, string palletCode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                _logger.LogInformation($"开始取走空箱 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                // 1. éªŒè¯ç©ºç®±å–走条件(必须全部完成拣选)
                var validationResult = await ValidateEmptyPalletRemoval(orderNo, palletCode);
                if (!validationResult.IsValid)
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                }
                var completedLocks = validationResult.Data;
                // 2. æ¸…理已完成的锁定记录(标记为已取走)
                await CleanupCompletedLocks(completedLocks);
                // 3. æ¸…理对应的库存记录状态
                foreach (var lockInfo in completedLocks)
                {
                    await CleanupStockInfo(lockInfo);
                }
                // 4. æ›´æ–°ç›¸å…³è®¢å•状态
                await UpdateOrderStatusAfterPalletRemoval(orderNo);
                // 5. è®°å½•操作历史
                await RecordEmptyPalletRemoval(orderNo, palletCode, completedLocks);
                _unitOfWorkManage.CommitTran();
                _logger.LogInformation($"取走空箱成功 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                return WebResponseContent.Instance.OK("取走空箱成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"取走空箱失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取走空箱失败:{ex.Message}");
            }
        }
        /// <summary>
        /// æ”¶é›†éœ€è¦å›žåº“的条码
        /// </summary>
        private async Task<List<string>> CollectReturnBarcodes(PalletStatusAnalysis status)
        {
            var returnBarcodes = new HashSet<string>();
            try
            {
                _logger.LogInformation($"开始收集回库条码 - è®¢å•: {status.OrderNo}, æ‰˜ç›˜: {status.PalletCode}");
                // 1. æ”¶é›†æœªåˆ†æ‹£é”å®šè®°å½•的条码
                if (status.HasRemainingLocks)
                {
                    foreach (var lockInfo in status.RemainingLocks)
                    {
                        if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
                        {
                            returnBarcodes.Add(lockInfo.CurrentBarcode);
                            _logger.LogInformation($"添加锁定记录条码: {lockInfo.CurrentBarcode}");
                        }
                    }
                }
                // 2. æ”¶é›†æ‰˜ç›˜ä¸Šåº“存货物的条码
                if (status.HasPalletStockGoods)
                {
                    foreach (var stockDetail in status.PalletStockGoods)
                    {
                        if (!string.IsNullOrEmpty(stockDetail.Barcode) && stockDetail.StockQuantity > 0)
                        {
                            returnBarcodes.Add(stockDetail.Barcode);
                            _logger.LogInformation($"添加库存货物条码: {stockDetail.Barcode}, æ•°é‡: {stockDetail.StockQuantity}");
                        }
                    }
                }
                // 3. æ”¶é›†æ‹†åŒ…记录相关的条码
                if (status.HasSplitRecords)
                {
                    foreach (var splitRecord in status.SplitRecords)
                    {
                        // æ·»åŠ åŽŸæ¡ç 
                        if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode))
                        {
                            var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                                .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == status.StockId);
                            if (originalStock != null && originalStock.StockQuantity > 0)
                            {
                                returnBarcodes.Add(splitRecord.OriginalBarcode);
                                _logger.LogInformation($"添加拆包原条码: {splitRecord.OriginalBarcode}, æ•°é‡: {originalStock.StockQuantity}");
                            }
                        }
                        // æ·»åŠ æ–°æ¡ç 
                        if (!string.IsNullOrEmpty(splitRecord.NewBarcode))
                        {
                            var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                                .FirstAsync(x => x.Barcode == splitRecord.NewBarcode && x.StockId == status.StockId);
                            if (newStock != null && newStock.StockQuantity > 0)
                            {
                                returnBarcodes.Add(splitRecord.NewBarcode);
                                _logger.LogInformation($"添加拆包新条码: {splitRecord.NewBarcode}, æ•°é‡: {newStock.StockQuantity}");
                            }
                        }
                    }
                }
                _logger.LogInformation($"回库条码收集完成 - å…± {returnBarcodes.Count} ä¸ªæ¡ç : {string.Join(", ", returnBarcodes)}");
                return returnBarcodes.ToList();
            }
            catch (Exception ex)
            {
                _logger.LogError($"收集回库条码失败 - Error: {ex.Message}");
                return returnBarcodes.ToList();
            }
        }
        #endregion
        #region å›žåº“操作核心方法
        /// <summary>
        /// æ‰§è¡Œå›žåº“操作 - å¢žå¼ºç‰ˆæœ¬
        /// </summary>
        private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo,
            Dt_Task task, PalletStatusAnalysis statusAnalysis)
        {
            _logger.LogInformation($"开始执行回库操作 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
            //  å¤„理未分拣的锁定记录
            if (statusAnalysis.HasRemainingLocks)
            {
                _logger.LogInformation($"处理 {statusAnalysis.RemainingLocks.Count} æ¡æœªåˆ†æ‹£é”å®šè®°å½•");
                await HandleRemainingLocksReturn(statusAnalysis.RemainingLocks);
            }
            //处理托盘上的库存货物
            if (statusAnalysis.HasPalletStockGoods)
            {
                _logger.LogInformation($"处理 {statusAnalysis.PalletStockGoods.Count} ä¸ªåº“存货物");
                await HandlePalletStockGoodsReturn(statusAnalysis.PalletStockGoods, stockInfo.Id);
            }
            //处理拆包记录
            if (statusAnalysis.HasSplitRecords)
            {
                _logger.LogInformation($"处理 {statusAnalysis.SplitRecords.Count} æ¡æ‹†åŒ…记录");
                await HandleSplitRecordsReturn(statusAnalysis.SplitRecords, stockInfo.Id);
            }
            _logger.LogInformation($"回库操作完成 - æ€»å›žåº“数量: {statusAnalysis.TotalReturnQty}");
        }
        /// <summary>
        /// å¤„理未分拣的锁定记录回库
        /// </summary>
        /// <summary>
        /// å¤„理托盘上的库存货物回库
        /// </summary>
        private async Task HandlePalletStockGoodsReturn(List<Dt_StockInfoDetail> palletStockGoods, int stockId)
        {
            foreach (var stockDetail in palletStockGoods)
            {
                // åªå¤„理出库锁定状态的库存
                if (stockDetail.Status == (int)StockStatusEmun.出库锁定 && stockDetail.StockQuantity > 0)
                {
                    // æ¢å¤åº“存状态为可用状态
                    stockDetail.Status = (int)StockStatusEmun.入库完成;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    _logger.LogInformation($"恢复库存货物 - æ¡ç : {stockDetail.Barcode}, æ•°é‡: {stockDetail.StockQuantity}");
                }
            }
        }
        /// <summary>
        /// å¤„理拆包记录回库
        /// </summary>
        private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, int stockId)
        {
            foreach (var splitRecord in splitRecords)
            {
                // å¤„理新条码
                var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == splitRecord.NewBarcode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .FirstAsync();
                if (newLockInfo != null)
                {
                    await HandleSingleLockReturn(newLockInfo);
                }
                // å¤„理原条码
                var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == splitRecord.OriginalBarcode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .FirstAsync();
                if (originalLockInfo != null)
                {
                    await HandleSingleLockReturn(originalLockInfo);
                }
                // æ›´æ–°æ‹†åŒ…记录状态为已回库
                splitRecord.Status = (int)SplitPackageStatusEnum.已回库;
                await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
            }
        }
        private async Task HandleSingleLockReturn(Dt_OutStockLockInfo lockInfo)
        {
            decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
            if (returnQty <= 0)
            {
                _logger.LogInformation($"跳过锁定记录 - é”å®šID: {lockInfo.Id}, å·²æ‹£é€‰å®Œæˆæˆ–无需回库");
                return;
            }
            _logger.LogInformation($"处理拆包相关锁定记录 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, å›žåº“数量: {returnQty}");
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
            if (stockDetail != null)
            {
                decimal originalStockQty = stockDetail.StockQuantity;
                decimal originalOutboundQty = stockDetail.OutboundQuantity;
                stockDetail.StockQuantity += returnQty;
                stockDetail.OutboundQuantity -= returnQty;
                if (stockDetail.OutboundQuantity < 0)
                {
                    stockDetail.OutboundQuantity = 0;
                }
                stockDetail.Status = (int)StockStatusEmun.入库完成;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"恢复拆包相关库存 - æ¡ç : {stockDetail.Barcode}, " +
                                     $"库存数量: {originalStockQty} -> {stockDetail.StockQuantity}");
            }
            // æ›´æ–°é”å®šçŠ¶æ€
            lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // å‡å°‘订单明细的分配数量
            if (lockInfo.OrderDetailId > 0)
            {
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
                if (orderDetail != null)
                {
                    await ReduceOrderDetailAllocation(orderDetail, returnQty);
                }
            }
            _logger.LogInformation($"拆包相关锁定记录回库完成 - é”å®šID: {lockInfo.Id}, å›žåº“数量: {returnQty}");
        }
        /// <summary>
        /// é‡Šæ”¾æ‰€æœ‰é”å®šä»¥ä¾¿é‡æ–°åˆ†é…
        /// </summary>
        private async Task ReleaseAllLocksForReallocation(string orderNo, string palletCode, PalletStatusAnalysis statusAnalysis)
        {
            // æ›´æ–°è®¢å•明细的已分配数量
            if (statusAnalysis.HasRemainingLocks)
            {
                var orderDetailGroups = statusAnalysis.RemainingLocks.GroupBy(x => x.OrderDetailId);
                foreach (var group in orderDetailGroups)
                {
                    var orderDetailId = group.Key;
                    var returnedQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                        .FirstAsync(x => x.Id == orderDetailId);
                    if (orderDetail != null)
                    {
                        orderDetail.AllocatedQuantity -= returnedQty;
                        orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
                        await UpdateBatchAllocateStatus(orderDetail);
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                        _logger.LogInformation($"更新订单明细 - OrderDetailId: {orderDetailId}, å‡å°‘分配数量: {returnedQty}");
                    }
                }
            }
        }
        #endregion
        #region è¾…助方法
        /// <summary>
        /// å¤„理没有回库物品的情况
        /// </summary>
        private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode, Dt_Task originalTask, int stockId)
        {
            _logger.LogInformation($"托盘 {palletCode} æ²¡æœ‰éœ€è¦å›žåº“的物品");
            // æ£€æŸ¥æ˜¯å¦æ˜¯ç©ºæ‰˜ç›˜
            var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockId);
            if (statusAnalysis.IsEmptyPallet)
            {
                try
                {
                    var locationtype = 0;
                    var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                            .Where(x => x.PalletCode == palletCode)
                            .FirstAsync();
                    if (stockInfo == null)
                    {
                        var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>().FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
                        locationtype = firstLocation?.LocationType ?? 1;
                    }
                    else
                    {
                        locationtype = stockInfo.LocationType;
                        _stockInfoService.DeleteData(stockInfo);
                    }
                    var targetAddress = originalTask.TargetAddress;
                    await CleanupZeroStockData(stockId);
                    var emptystockInfo = new Dt_StockInfo() { PalletType = PalletTypeEnum.Empty.ObjToInt(), StockStatus = StockStatusEmun.组盘暂存.ObjToInt(), PalletCode = palletCode, LocationType = locationtype };
                    emptystockInfo.Details = new List<Dt_StockInfoDetail>();
                    _stockInfoService.AddMaterielGroup(emptystockInfo);
                    //空托盘如何处理  è¿˜æœ‰ä¸€ä¸ªå‡ºåº“任务要处理。
                    originalTask.PalletType = PalletTypeEnum.Empty.ObjToInt();
                    await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt());
                }
                catch (Exception ex)
                {
                    _logger.LogError($" HandleNoReturnItems  å¤±è´¥: {ex.Message}");
                    return WebResponseContent.Instance.Error($" å›žåº“空托盘失败!");
                }
                return WebResponseContent.Instance.OK("空托盘回库任务创建成功");
            }
            else
            {
                return WebResponseContent.Instance.Error("托盘状态异常:有物品但无法计算回库数量");
            }
        }
        private async Task CleanupZeroStockData(int stockId)
        {
            try
            {
                // 1. åˆ é™¤åº“存数量为0的明细记录
                var deleteDetailCount = await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
            .Where(x => x.StockId == stockId && x.StockQuantity == 0)
            .ExecuteCommandAsync();
                await _stockInfoService.Db.Deleteable<Dt_StockInfo>()
                   .Where(x => x.Id == stockId).ExecuteCommandAsync();
                _logger.LogInformation($"清理零库存明细记录 - StockId: {stockId}, åˆ é™¤è®°å½•æ•°: {deleteDetailCount}");
            }
            catch (Exception ex)
            {
                _logger.LogWarning($"清理零库存数据失败 - StockId: {stockId}, Error: {ex.Message}");
                // æ³¨æ„ï¼šæ¸…理失败不应该影响主流程
            }
        }
        /// <summary>
        /// æ›´æ–°è®¢å•状态(回库后)
        /// </summary>
        private async Task UpdateOrderStatusForReturn(string orderNo)
        {
            // æ£€æŸ¥è®¢å•是否所有托盘都已完成或已回库
            var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo)
                .ToListAsync();
            var activeLocks = allLocks.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.出库中 ||
                x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
            // å¦‚果没有活跃的锁定记录,更新订单状态
            if (!activeLocks.Any())
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => new Dt_OutboundOrder
                    {
                        OrderStatus = (int)OutOrderStatusEnum.出库完成,
                    })
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
                _logger.LogInformation($"更新订单状态为出库完成 - è®¢å•: {orderNo}");
            }
        }
        #endregion
        #region éªŒè¯æ–¹æ³•
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>> ValidatePickingRequest(
       string orderNo, string palletCode, string barcode)
        {
            _logger.LogInformation($"开始验证分拣请求 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, æ¡ç : {barcode}");
            // æŸ¥æ‰¾é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
@@ -972,6 +2837,13 @@
            if (lockInfo == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("未找到有效的锁定信息");
            if (lockInfo.IsUnallocated == 1 || lockInfo.OrderDetailId == 0)
            {
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("该条码是未分配锁定记录,不能直接分拣");
            }
            _logger.LogInformation($"找到锁定信息 - åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}, å·²æ‹£é€‰: {lockInfo.PickedQty}");
            // æ£€æŸ¥æ˜¯å¦å·²ç»åˆ†æ‹£å®Œæˆ
            if (lockInfo.PickedQty >= lockInfo.AssignQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("该条码已分拣完成");
@@ -980,132 +2852,130 @@
            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, Dt_OutboundBatch)>.Error("未找到订单明细");
            _logger.LogInformation($"找到订单明细 - å·²åˆ†é…æ•°é‡: {orderDetail.AllocatedQuantity}, é”å®šæ•°é‡: {orderDetail.LockQuantity}");
            // æ£€æŸ¥è®¢å•明细的已分配数量是否足够
            decimal remainingToPick = lockInfo.AssignQuantity - lockInfo.PickedQty;
            // è¿™é‡Œåº”该检查锁定信息的分配数量,而不是订单明细的分配数量
            // å› ä¸ºæ‹†åŒ…后,锁定信息的分配数量可能小于订单明细的分配数量
            if (lockInfo.AssignQuantity < remainingToPick)
            {
                _logger.LogWarning($"锁定信息分配数量不足 - éœ€è¦æ‹£é€‰: {remainingToPick}, é”å®šä¿¡æ¯åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
                    $"锁定信息分配数量不足,需要拣选:{remainingToPick},锁定信息分配数量:{lockInfo.AssignQuantity}");
            }
            // æ£€æŸ¥é”å®šæ•°é‡æ˜¯å¦è¶³å¤Ÿ
            if (orderDetail.LockQuantity < remainingToPick)
            {
                _logger.LogWarning($"订单明细锁定数量不足 - éœ€è¦æ‹£é€‰: {remainingToPick}, å¯ç”¨é”å®šæ•°é‡: {orderDetail.LockQuantity}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
                    $"订单明细锁定数量不足,需要拣选:{remainingToPick},可用锁定数量:{orderDetail.LockQuantity}");
            }
            // æ£€æŸ¥è®¢å•明细分配数量是否为负数
            if (orderDetail.AllocatedQuantity < 0)
            {
                _logger.LogError($"订单明细分配数量为负数 - å½“前值: {orderDetail.AllocatedQuantity}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
                    $"订单明细分配数量异常(负数),请联系管理员处理");
            }
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == barcode && x.StockId == lockInfo.StockId);
            if (stockDetail == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("未找到对应的库存信息");
            // éªŒè¯åº“存数量
            if (stockDetail.StockQuantity < lockInfo.AssignQuantity)
            if (stockDetail.StockQuantity < remainingToPick)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
                    $"库存数量不足,需要:{lockInfo.AssignQuantity},实际:{stockDetail.StockQuantity}");
                    $"库存数量不足,需要拣选:{remainingToPick},实际库存:{stockDetail.StockQuantity}");
            // éªŒè¯åº“存状态
            //if (stockDetail.Status != (int)StockStatusEmun.出库锁定)
            //{
            //    return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
            //        $"库存状态异常,当前状态:{stockDetail.Status},期望状态:出库锁定");
            //}
            // ä½¿ç”¨ OutboundBatchNo æŸ¥æ‰¾æ‰¹æ¬¡
            var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
                .FirstAsync(x => x.BatchNo == lockInfo.OutboundBatchNo); // ä¿®æ­£ä¸º OutboundBatchNo
                .FirstAsync(x => x.BatchNo == lockInfo.OutboundBatchNo);
            _logger.LogInformation($"分拣验证通过 - æ¡ç : {barcode}, å‰©ä½™éœ€æ‹£é€‰: {remainingToPick}, å¯ç”¨åº“å­˜: {stockDetail.StockQuantity}");
            return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Success((lockInfo, orderDetail, stockDetail, batch));
        }
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateSplitRequest(
            string orderNo, string palletCode, string originalBarcode, decimal splitQuantity)
        /// <summary>
        /// æ£€æŸ¥å¹¶æ‰§è¡Œè‡ªåŠ¨æ‹†åŒ…ï¼ˆå¦‚æžœéœ€è¦ï¼‰
        /// </summary>
        private async Task<List<SplitResult>> CheckAndAutoSplitIfNeeded(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, string palletCode)
        {
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.CurrentBarcode == originalBarcode &&
                           x.Status == (int)OutLockStockStatusEnum.出库中)
                .FirstAsync();
            if (lockInfo.IsUnallocated == 1 || lockInfo.OrderDetailId == 0)
            {
                _logger.LogInformation($"跳过未分配锁定记录的自动拆包检查 - é”å®šID: {lockInfo.Id}");
                return null;
            }
            // æ£€æŸ¥æ˜¯å¦éœ€è¦è‡ªåŠ¨æ‹†åŒ…çš„æ¡ä»¶ï¼š
            // 1. åº“存数量大于分配数量
            // 2. é”å®šä¿¡æ¯çŠ¶æ€ä¸ºå‡ºåº“ä¸­
            // 3. æœªæ‹£é€‰æ•°é‡ç­‰äºŽåˆ†é…æ•°é‡ï¼ˆè¡¨ç¤ºè¿˜æœªå¼€å§‹æ‹£é€‰ï¼‰
            bool needAutoSplit = stockDetail.StockQuantity > lockInfo.AssignQuantity &&
                                lockInfo.Status == (int)OutLockStockStatusEnum.出库中 &&
                                lockInfo.PickedQty == 0;
            if (lockInfo == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到有效的锁定信息");
            if (!needAutoSplit)
                return null;
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == originalBarcode && x.StockId == lockInfo.StockId);
            // è®¡ç®—拆包数量 = åº“存数量 - åˆ†é…æ•°é‡
            decimal splitQuantity = stockDetail.StockQuantity - lockInfo.AssignQuantity;
            if (stockDetail.StockQuantity < splitQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("拆包数量不能大于库存数量");
            // æ‰§è¡Œè‡ªåŠ¨æ‹†åŒ…
            var splitResult = await ExecuteAutoSplitLogic(lockInfo, stockDetail, splitQuantity, palletCode);
            if (lockInfo.AssignQuantity - lockInfo.PickedQty < splitQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("拆包数量不能大于未拣选数量");
            return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((lockInfo, stockDetail));
            return splitResult;
        }
        #endregion
        #region æ ¸å¿ƒé€»è¾‘方法
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
            Dt_StockInfoDetail stockDetail, decimal actualPickedQty)
        {
            // æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += actualPickedQty;
            if (lockInfo.PickedQty >= lockInfo.AssignQuantity)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
            }
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ›´æ–°åº“å­˜
            stockDetail.StockQuantity -= actualPickedQty;
            stockDetail.OutboundQuantity += actualPickedQty;
            if (stockDetail.StockQuantity <= 0)
            {
                stockDetail.Status = (int)StockStatusEmun.出库完成;
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            return new PickingResult
            {
                FinalLockInfo = lockInfo,
                ActualPickedQty = actualPickedQty
            };
        }
        private async Task<RevertPickingResult> RevertPickingData(Dt_PickingRecord pickingRecord)
        {
            // æ¢å¤é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.Id == pickingRecord.OutStockLockId);
            lockInfo.PickedQty -= pickingRecord.PickQuantity;
            // æ ¹æ®æ‹£é€‰æ•°é‡åˆ¤æ–­çŠ¶æ€
            if (lockInfo.PickedQty <= 0)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            }
            else if (lockInfo.PickedQty < lockInfo.AssignQuantity)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            }
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == pickingRecord.Barcode);
            stockDetail.StockQuantity += pickingRecord.PickQuantity;
            stockDetail.OutboundQuantity -= pickingRecord.PickQuantity;
            // æ¢å¤åº“存状态
            if (stockDetail.StockQuantity > 0)
            {
                stockDetail.Status = (int)StockStatusEmun.出库锁定;
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            return new RevertPickingResult
            {
                LockInfo = lockInfo,
                StockDetail = stockDetail
            };
        }
        private async Task<SplitResultDto> ExecuteSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
        /// <summary>
        /// æ‰§è¡Œè‡ªåŠ¨æ‹†åŒ…é€»è¾‘
        /// ç¡®ä¿è‡ªåŠ¨æ‹†åŒ…ä¸ä¼šå½±å“å›žåº“é€»è¾‘
        /// </summary>
        private async Task<List<SplitResult>> ExecuteAutoSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
     decimal splitQuantity, string palletCode)
        {
            _logger.LogInformation($"开始执行自动拆包逻辑 - åŽŸæ¡ç : {stockDetail.Barcode}, æ‹†åŒ…数量: {splitQuantity}");
            // éªŒè¯æ‹†åŒ…数量合理性
            if (splitQuantity <= 0)
            {
                throw new InvalidOperationException($"拆包数量必须大于0,当前值: {splitQuantity}");
            }
            if (stockDetail.StockQuantity < lockInfo.AssignQuantity + splitQuantity)
            {
                throw new InvalidOperationException($"库存数量不足以进行自动拆包");
            }
            // ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
            // åˆ›å»ºæ–°åº“存明细
            // è®°å½•拆包前的分配数量
            decimal originalAssignQty = lockInfo.AssignQuantity;
            decimal remainQty = originalAssignQty; // åŽŸé”å®šä¿¡æ¯åˆ†é…æ•°é‡ä¿æŒä¸å˜
            // åˆ›å»ºæ–°åº“存明细(多余部分)
            var newStockDetail = new Dt_StockInfoDetail
            {
                StockId = stockDetail.StockId,
                MaterielCode = stockDetail.MaterielCode,
                OrderNo = stockDetail.OrderNo,
                BatchNo = stockDetail.BatchNo, // ç‰©æ–™æ‰¹æ¬¡
                BatchNo = stockDetail.BatchNo,
                StockQuantity = splitQuantity,
                OutboundQuantity = 0,
                Barcode = newBarcode,
@@ -1118,17 +2988,14 @@
                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
            };
            await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
            _logger.LogInformation($"创建新库存明细 - æ¡ç : {newBarcode}, åº“存数量: {splitQuantity}");
            // æ›´æ–°åŽŸåº“å­˜æ˜Žç»†
            stockDetail.StockQuantity -= splitQuantity;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // åˆ›å»ºæ–°é”å®šä¿¡æ¯ - ä½¿ç”¨æ­£ç¡®çš„ OutboundBatchNo
            // åˆ›å»ºæ–°é”å®šä¿¡æ¯ï¼ˆå¤šä½™éƒ¨åˆ†ï¼‰- æ ‡è®°ä¸ºæœªåˆ†é…
            var newLockInfo = new Dt_OutStockLockInfo
            {
                OrderNo = lockInfo.OrderNo,
                OrderDetailId = lockInfo.OrderDetailId,
                OutboundBatchNo = lockInfo.OutboundBatchNo, // ä½¿ç”¨ OutboundBatchNo
                OrderDetailId = 0, // é‡è¦ï¼šä¸ç»‘定到具体订单明细
                OutboundBatchNo = lockInfo.OutboundBatchNo,
                MaterielCode = lockInfo.MaterielCode,
                MaterielName = lockInfo.MaterielName,
                StockId = lockInfo.StockId,
@@ -1151,52 +3018,223 @@
                WarehouseCode = lockInfo.WarehouseCode,
                BarcodeQty = lockInfo.BarcodeQty,
                BarcodeUnit = lockInfo.BarcodeUnit,
                IsUnallocated = 1 // æ ‡è®°ä¸ºæœªåˆ†é…
            };
            await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
            _logger.LogInformation($"创建新锁定信息 - æ¡ç : {newBarcode}, åˆ†é…æ•°é‡: {splitQuantity}, æ ‡è®°ä¸ºæœªåˆ†é…");
            // æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity -= splitQuantity;
            lockInfo.OrderQuantity -= splitQuantity;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // è‡ªåŠ¨æ‹†åŒ…ä¸æ”¹å˜è®¢å•æ˜Žç»†çš„åˆ†é…æ•°é‡
            _logger.LogInformation($"自动拆包 - è®¢å•明细分配数量保持不变");
            // è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode);
            await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, true, stockDetail.StockQuantity);
            return new SplitResultDto { NewBarcode = newBarcode };
            // åˆ›å»ºæ‹†åŒ…结果列表
            var splitResults = CreateSplitResults(lockInfo, splitQuantity, remainQty, newBarcode, stockDetail.Barcode);
            _logger.LogInformation($"自动拆包逻辑执行完成");
            return splitResults;
        }
        private async Task ExecuteCancelSplitLogic(Dt_SplitPackageRecord splitRecord, Dt_OutStockLockInfo newLockInfo, Dt_StockInfoDetail newStockDetail)
        /// <summary>
        /// éªŒè¯è‡ªåŠ¨æ‹†åŒ…åŽæ•°æ®ä¸€è‡´æ€§
        /// </summary>
        private async Task ValidateDataConsistencyAfterAutoSplit(long orderDetailId, decimal originalAllocatedQty, decimal originalLockQty, decimal splitQuantity)
        {
            // æ¢å¤åŽŸåº“å­˜
            var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
            // é‡æ–°èŽ·å–è®¢å•æ˜Žç»†æ•°æ®
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            originalStock.StockQuantity += splitRecord.SplitQty;
            await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
            if (orderDetail == null)
                return;
            // æ¢å¤åŽŸé”å®šä¿¡æ¯
            var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
            // è®¡ç®—所有锁定信息的总分配数量
            var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId)
                .ToListAsync();
            originalLockInfo.AssignQuantity += splitRecord.SplitQty;
            originalLockInfo.OrderQuantity += splitRecord.SplitQty;
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
            decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
            // åˆ é™¤æ–°åº“存明细
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == newLockInfo.CurrentBarcode)
                .ExecuteCommandAsync();
            _logger.LogInformation($"自动拆包后数据一致性验证 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQty}");
            // åˆ é™¤æ–°é”å®šä¿¡æ¯
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == newLockInfo.Id)
                .ExecuteCommandAsync();
            // éªŒè¯è‡ªåŠ¨æ‹†åŒ…åŽçš„æ•°æ®ä¸€è‡´æ€§
            decimal expectedAllocatedQty = originalAllocatedQty + splitQuantity;
            decimal expectedLockQty = originalLockQty + splitQuantity;
            // æ ‡è®°æ‹†åŒ…记录为已撤销
            splitRecord.IsReverted = true;
            splitRecord.RevertTime = DateTime.Now;
            splitRecord.RevertOperator = App.User.UserName;
            await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
            if (Math.Abs(orderDetail.AllocatedQuantity - expectedAllocatedQty) > 0.01m)
            {
                _logger.LogWarning($"自动拆包后分配数量异常 - æœŸæœ›: {expectedAllocatedQty}, å®žé™…: {orderDetail.AllocatedQuantity}");
            }
            if (Math.Abs(orderDetail.LockQuantity - expectedLockQty) > 0.01m)
            {
                _logger.LogWarning($"自动拆包后锁定数量异常 - æœŸæœ›: {expectedLockQty}, å®žé™…: {orderDetail.LockQuantity}");
            }
            if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
            {
                _logger.LogWarning($"自动拆包后数据不一致 - è®¢å•明细分配数量: {orderDetail.AllocatedQuantity}, é”å®šä¿¡æ¯æ€»åˆ†é…æ•°é‡: {totalLockAssignQty}");
            }
        }
        #endregion
        #region æ ¸å¿ƒé€»è¾‘方法
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
            Dt_StockInfoDetail stockDetail, decimal actualPickedQty)
        {
            _logger.LogInformation($"开始执行分拣逻辑 - æ¡ç : {stockDetail.Barcode}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}, å®žé™…拣选: {actualPickedQty}");
            // å†æ¬¡éªŒè¯è®¢å•明细的分配数量(防止并发操作)
            if (orderDetail.AllocatedQuantity < actualPickedQty)
            {
                throw new InvalidOperationException($"订单明细分配数量不足,需要拣选 {actualPickedQty},可用分配数量 {orderDetail.AllocatedQuantity}");
            }
            if (orderDetail.LockQuantity < actualPickedQty)
            {
                throw new InvalidOperationException($"订单明细锁定数量不足,需要拣选 {actualPickedQty},可用锁定数量 {orderDetail.LockQuantity}");
            }
            // æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += actualPickedQty;
            _logger.LogInformation($"更新锁定信息 - å·²æ‹£é€‰æ•°é‡ä»Ž {lockInfo.PickedQty - actualPickedQty} å¢žåŠ åˆ° {lockInfo.PickedQty}");
            // å‡†ç¡®åˆ¤æ–­æ‹£é€‰å®ŒæˆçŠ¶æ€
            if (Math.Abs(lockInfo.PickedQty - lockInfo.AssignQuantity) < 0.001m)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
                _logger.LogInformation($"锁定信息状态更新为拣选完成");
            }
            else if (lockInfo.PickedQty > 0)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
                _logger.LogInformation($"锁定信息状态保持为出库中(部分拣选)");
            }
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ›´æ–°åº“存信息
            decimal originalStockQty = stockDetail.StockQuantity;
            decimal originalOutboundQty = stockDetail.OutboundQuantity;
            stockDetail.StockQuantity -= actualPickedQty;
            stockDetail.OutboundQuantity += actualPickedQty;
            _logger.LogInformation($"更新库存信息 - åº“存数量从 {originalStockQty} å‡å°‘到 {stockDetail.StockQuantity}");
            _logger.LogInformation($"更新库存信息 - å‡ºåº“数量从 {originalOutboundQty} å¢žåŠ åˆ° {stockDetail.OutboundQuantity}");
            // å‡†ç¡®åˆ¤æ–­åº“存状态
            if (stockDetail.StockQuantity <= 0)
            {
                stockDetail.Status = (int)StockStatusEmun.出库完成;
                _logger.LogInformation($"库存状态更新为出库完成");
            }
            else
            {
                stockDetail.Status = (int)StockStatusEmun.出库锁定;
                _logger.LogInformation($"库存状态保持为出库锁定");
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            _logger.LogInformation($"分拣逻辑执行完成 - æ¡ç : {stockDetail.Barcode}");
            return new PickingResult
            {
                FinalLockInfo = lockInfo,
                ActualPickedQty = actualPickedQty
            };
        }
        private async Task<RevertPickingResult> RevertPickingData(Dt_PickingRecord pickingRecord)
        {
            _logger.LogInformation($"开始恢复拣选数据 - æ‹£é€‰è®°å½•ID: {pickingRecord.Id}, æ¡ç : {pickingRecord.Barcode}, æ‹£é€‰æ•°é‡: {pickingRecord.PickQuantity}");
            // 1. æ¢å¤é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.Id == pickingRecord.OutStockLockId);
            if (lockInfo == null)
            {
                throw new InvalidOperationException($"未找到对应的锁定信息,ID: {pickingRecord.OutStockLockId}");
            }
            decimal originalPickedQty = lockInfo.PickedQty;
            decimal originalAssignQty = lockInfo.AssignQuantity; // è®°å½•原始分配数量
            // åªæ¢å¤å·²æ‹£é€‰æ•°é‡ï¼Œä¸ä¿®æ”¹åˆ†é…æ•°é‡
            lockInfo.PickedQty -= pickingRecord.PickQuantity;
            // ç¡®ä¿å·²æ‹£é€‰æ•°é‡ä¸ä¼šä¸ºè´Ÿæ•°
            if (lockInfo.PickedQty < 0)
            {
                _logger.LogWarning($"已拣选数量出现负数,重置为0。原值: {lockInfo.PickedQty + pickingRecord.PickQuantity}, æ¢å¤æ•°é‡: {pickingRecord.PickQuantity}");
                lockInfo.PickedQty = 0;
            }
            _logger.LogInformation($"恢复锁定信息 - å·²æ‹£é€‰æ•°é‡ä»Ž {originalPickedQty} å‡å°‘到 {lockInfo.PickedQty}");
            _logger.LogInformation($"锁定信息分配数量保持不变: {originalAssignQty}");
            // æ¢å¤é”å®šçŠ¶æ€
            if (lockInfo.PickedQty <= 0)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
                _logger.LogInformation($"锁定信息状态恢复为出库中");
            }
            else if (lockInfo.PickedQty < lockInfo.AssignQuantity)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中; // éƒ¨åˆ†æ‹£é€‰çŠ¶æ€
                _logger.LogInformation($"锁定信息状态恢复为出库中(部分拣选)");
            }
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 2. æ¢å¤åº“存信息
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == pickingRecord.Barcode);
            if (stockDetail == null)
            {
                throw new InvalidOperationException($"未找到对应的库存信息,条码: {pickingRecord.Barcode}");
            }
            decimal originalStockQty = stockDetail.StockQuantity;
            decimal originalOutboundQty = stockDetail.OutboundQuantity;
            stockDetail.StockQuantity += pickingRecord.PickQuantity;
            stockDetail.OutboundQuantity -= pickingRecord.PickQuantity;
            // ç¡®ä¿å‡ºåº“数量不会为负数
            if (stockDetail.OutboundQuantity < 0)
            {
                _logger.LogWarning($"出库数量出现负数,重置为0。原值: {stockDetail.OutboundQuantity + pickingRecord.PickQuantity}, æ¢å¤æ•°é‡: {pickingRecord.PickQuantity}");
                stockDetail.OutboundQuantity = 0;
            }
            _logger.LogInformation($"恢复库存信息 - åº“存数量从 {originalStockQty} å¢žåŠ åˆ° {stockDetail.StockQuantity}");
            _logger.LogInformation($"恢复库存信息 - å‡ºåº“数量从 {originalOutboundQty} å‡å°‘到 {stockDetail.OutboundQuantity}");
            // æ¢å¤åº“存状态
            if (stockDetail.StockQuantity > 0)
            {
                stockDetail.Status = (int)StockStatusEmun.出库锁定;
                _logger.LogInformation($"库存状态恢复为出库锁定");
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            _logger.LogInformation($"恢复拣选数据完成 - æ¡ç : {pickingRecord.Barcode}");
            return new RevertPickingResult
            {
                LockInfo = lockInfo,
                StockDetail = stockDetail
            };
        }
        #endregion
@@ -1205,56 +3243,144 @@
        private async Task UpdateBatchAndOrderData(Dt_OutboundBatch batch, Dt_OutboundOrderDetail orderDetail, decimal pickedQty, string orderNo)
        {
            // æ›´æ–°æ‰¹æ¬¡å®Œæˆæ•°é‡
            _logger.LogInformation($"开始更新批次和订单数据 - æ‹£é€‰æ•°é‡: {pickedQty}");
            var latestOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().FirstAsync(x => x.Id == orderDetail.Id);
            if (latestOrderDetail == null)
                throw new InvalidOperationException("未找到订单明细");
            orderDetail = latestOrderDetail;
            // éªŒè¯åˆ†é…æ•°é‡ä¸ä¼šå˜æˆè´Ÿæ•°
            if (orderDetail.AllocatedQuantity < pickedQty)
            {
                decimal actualPickedQty = orderDetail.AllocatedQuantity;
                _logger.LogWarning($"分配数量不足,调整拣选数量 - åŽŸéœ€è¦: {pickedQty}, å®žé™…可用: {actualPickedQty}");
                pickedQty = actualPickedQty;
            }
            if (orderDetail.LockQuantity < pickedQty)
            {
                decimal actualPickedQty = orderDetail.LockQuantity;
                _logger.LogWarning($"锁定数量不足,调整拣选数量 - åŽŸéœ€è¦: {pickedQty}, å®žé™…可用: {actualPickedQty}");
                pickedQty = actualPickedQty;
            }
            // 1. æ›´æ–°æ‰¹æ¬¡å®Œæˆæ•°é‡
            decimal originalBatchCompletedQty = batch.CompletedQuantity;
            batch.CompletedQuantity += pickedQty;
            _logger.LogInformation($"更新批次完成数量 - ä»Ž {originalBatchCompletedQty} å¢žåŠ åˆ° {batch.CompletedQuantity}");
            if (batch.CompletedQuantity >= batch.BatchQuantity)
            {
                batch.BatchStatus = (int)BatchStatusEnum.已完成;
                _logger.LogInformation($"批次状态更新为已完成");
            }
            await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
            // æ›´æ–°è®¢å•明细
            decimal originalOverOutQty = orderDetail.OverOutQuantity;
            decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
            decimal originalLockQty = orderDetail.LockQuantity;
            orderDetail.OverOutQuantity += pickedQty;
            orderDetail.AllocatedQuantity -= pickedQty;
            // LockQuantity åŒæ­¥å‡å°‘
            orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
            if (orderDetail.AllocatedQuantity < 0) orderDetail.AllocatedQuantity = 0;
            if (orderDetail.LockQuantity < 0) orderDetail.LockQuantity = 0;
            _logger.LogInformation($"更新订单明细 - å·²å‡ºåº“数量从 {originalOverOutQty} å¢žåŠ åˆ° {orderDetail.OverOutQuantity}");
            _logger.LogInformation($"更新订单明细 - å·²åˆ†é…æ•°é‡ä»Ž {originalAllocatedQty} å‡å°‘到 {orderDetail.AllocatedQuantity}");
            _logger.LogInformation($"更新订单明细 - é”å®šæ•°é‡ä»Ž {originalLockQty} å‡å°‘到 {orderDetail.LockQuantity}");
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            // æ£€æŸ¥è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
            _logger.LogInformation($"批次和订单数据更新完成");
        }
        private async Task RevertBatchAndOrderData(Dt_PickingRecord pickingRecord, RevertPickingResult revertResult)
        {
            // æ¢å¤æ‰¹æ¬¡å®Œæˆæ•°é‡
            _logger.LogInformation($"开始恢复批次和订单数据");
            //   æ¢å¤æ‰¹æ¬¡å®Œæˆæ•°é‡
            var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
                .FirstAsync(x => x.BatchNo == revertResult.LockInfo.OutboundBatchNo); // ä½¿ç”¨ OutboundBatchNo
                .FirstAsync(x => x.BatchNo == revertResult.LockInfo.OutboundBatchNo);
            if (batch != null)
            {
                decimal originalCompletedQty = batch.CompletedQuantity;
                batch.CompletedQuantity -= pickingRecord.PickQuantity;
                if (batch.CompletedQuantity < 0)
                {
                    batch.CompletedQuantity = 0;
                    _logger.LogWarning($"批次完成数量出现负数,重置为0");
                }
                _logger.LogInformation($"恢复批次完成数量 - ä»Ž {originalCompletedQty} å‡å°‘到 {batch.CompletedQuantity}");
                // é‡æ–°è®¡ç®—批次状态
                if (batch.CompletedQuantity <= 0)
                {
                    batch.BatchStatus = (int)BatchStatusEnum.分配中;
                    _logger.LogInformation($"批次状态恢复为分配中");
                }
                else if (batch.CompletedQuantity < batch.BatchQuantity)
                {
                    batch.BatchStatus = (int)BatchStatusEnum.执行中;
                    _logger.LogInformation($"批次状态恢复为执行中");
                }
                await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
            }
            // æ¢å¤è®¢å•明细
            //   æ¢å¤è®¢å•明细
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
            orderDetail.OverOutQuantity -= pickingRecord.PickQuantity;
            orderDetail.AllocatedQuantity += pickingRecord.PickQuantity;
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            if (orderDetail != null)
            {
                decimal originalOverOutQty = orderDetail.OverOutQuantity;
                decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
                decimal originalLockQty = orderDetail.LockQuantity;
            // é‡æ–°æ£€æŸ¥è®¢å•状态
                // åªæ¢å¤ç›¸å…³æ•°é‡ï¼Œåˆ†é…æ•°é‡ä¿æŒä¸å˜
                orderDetail.OverOutQuantity -= pickingRecord.PickQuantity;
                orderDetail.AllocatedQuantity += pickingRecord.PickQuantity;
                orderDetail.LockQuantity += pickingRecord.PickQuantity;
                if (orderDetail.OverOutQuantity < 0) orderDetail.OverOutQuantity = 0;
                if (orderDetail.AllocatedQuantity < 0) orderDetail.AllocatedQuantity = 0;
                if (orderDetail.LockQuantity < 0) orderDetail.LockQuantity = 0;
                _logger.LogInformation($"恢复订单明细 - å·²å‡ºåº“数量从 {originalOverOutQty} å‡å°‘到 {orderDetail.OverOutQuantity}");
                _logger.LogInformation($"订单明细分配数量保持不变: {originalAllocatedQty}");
                _logger.LogInformation($"订单明细锁定数量保持不变: {originalLockQty}");
                await UpdateBatchAllocateStatus(orderDetail);
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            }
            // 3. é‡æ–°æ£€æŸ¥è®¢å•状态
            await CheckAndUpdateOrderStatus(pickingRecord.OrderNo);
            _logger.LogInformation($"恢复批次和订单数据完成");
        }
        private async Task UpdateBatchAllocateStatus(Dt_OutboundOrderDetail orderDetail)
        {
            if (orderDetail.AllocatedQuantity >= orderDetail.NeedOutQuantity)
            {
                orderDetail.BatchAllocateStatus = OrderDetailStatusEnum.AssignOver.ObjToInt();
            }
            else if (orderDetail.AllocatedQuantity > 0)
            {
                orderDetail.BatchAllocateStatus = OrderDetailStatusEnum.AssignOverPartial.ObjToInt();
            }
            else
            {
                orderDetail.BatchAllocateStatus = OrderDetailStatusEnum.New.ObjToInt();
            }
        }
        private async Task ReleaseLockAndStock(Dt_OutStockLockInfo lockInfo)
        {
@@ -1326,11 +3452,114 @@
                if (orderDetail != null)
                {
                    orderDetail.AllocatedQuantity -= returnedQty;
                    // LockQuantity åŒæ­¥å‡å°‘,保持与已分配数量一致
                    orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
                    await UpdateBatchAllocateStatus(orderDetail);
                    await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                }
            }
        }
        #endregion
        private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, TaskTypeEnum taskTypeEnum, int palletType)
        {
            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,
                OrderNo = orderNo,
                Roadway = newLocation.RoadwayNo,
                SourceAddress = stations[originalTask.TargetAddress],
                TargetAddress = newLocation.LocationCode,
                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                TaskType = taskTypeEnum.ObjToInt(),
                PalletType = palletType,
                WarehouseId = originalTask.WarehouseId
            };
            // ä¿å­˜å›žåº“任务
            var insertcount = await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
            if (insertcount <= 0)
            {
                throw new Exception("创建任务失败!");
            }
            var targetAddress = originalTask.TargetAddress;
            // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
            //_taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
            _task_HtyService.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.人工删除);
            await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            // ç»™ ESS å‘送流动信号和创建任务
            await SendESSCommands(palletCode, targetAddress, returnTask);
        }
        /// <summary>
        /// ç»™ESS下任务
        /// </summary>
        /// <param name="palletCode"></param>
        /// <param name="targetAddress"></param>
        /// <param name="returnTask"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        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[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(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}");
            }
        }
        #region è¾…助方法
@@ -1340,7 +3569,7 @@
            return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString().PadLeft(5, '0');
        }
        private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal splitQty, string newBarcode)
        private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal splitQty, string newBarcode, bool isAutoSplit, decimal? originalStockQuantity = null)
        {
            var splitHistory = new Dt_SplitPackageRecord
            {
@@ -1352,7 +3581,12 @@
                NewBarcode = newBarcode,
                SplitQty = splitQty,
                SplitTime = DateTime.Now,
                Status = (int)SplitPackageStatusEnum.已拆包
                Status = (int)SplitPackageStatusEnum.已拆包,
                IsAutoSplit = isAutoSplit,
                // SplitType = isAutoSplit ? "自动拆包" : "手动拆包"
                OriginalStockQuantity = originalStockQuantity ?? stockDetail.StockQuantity,
                //RemainingStockQuantity = stockDetail.StockQuantity - splitQty
                TaskNum = lockInfo.TaskNum
            };
            await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
@@ -1414,9 +3648,44 @@
            };
        }
        #endregion
        #region DTOç±»
        /// <summary>
        /// æ¡ç çŠ¶æ€ä¿¡æ¯DTO
        /// </summary>
        public class BarcodeStatusInfoDto
        {
            public string Barcode { get; set; }
            public string OrderNo { get; set; }
            public bool IsOriginalBarcode { get; set; }
            public int SplitChainCount { get; set; }
            public bool HasBeenPicked { get; set; }
            public decimal TotalPickedQuantity { get; set; }
            public int PickRecordCount { get; set; }
            public int LockInfoStatus { get; set; }
            public decimal LockInfoPickedQty { get; set; }
            public decimal LockInfoAssignQty { get; set; }
            public decimal StockQuantity { get; set; }
            public int StockStatus { get; set; }
            public bool CanCancelSplit { get; set; }
            public bool NeedCancelPickFirst { get; set; }
            public List<string> OperationSuggestions { get; set; } = new List<string>();
        }
        public class PickedBarcodeInfo
        {
            public string Barcode { get; set; }
            public decimal PickedQty { get; set; }
            public int PickRecordCount { get; set; }
        }
        /// <summary>
        /// è‡ªåŠ¨æ‹†åŒ…ç»“æžœ
        /// </summary>
        public class AutoSplitResult
        {
            public string NewBarcode { get; set; }
            public decimal SplitQuantity { get; set; }
        }
        public class PickingResult
        {
@@ -1446,12 +3715,5 @@
        }
        #endregion
    }
    // æ”¯æŒç±»
    public class SplitResultDto
    {
        public string NewBarcode { get; set; }
    }
}