pan
2025-11-27 1a1bc81ac1d9ad656e15b409122e23f7f3763293
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -20,9 +20,11 @@
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Allocate;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_DTO.Outbound;
using WIDESEA_IAllocateService;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
@@ -50,6 +52,7 @@
        private readonly IESSApiService _eSSApiService;
        private readonly IInvokeMESService _invokeMESService;
        private readonly IDailySequenceService _dailySequenceService;
        private readonly IAllocateService _allocateService;
        private readonly ILogger<OutboundPickingService> _logger;
@@ -70,7 +73,7 @@
        public OutboundPickingService(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) : base(BaseDal)
            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -86,6 +89,7 @@
            _logger = logger;
            _invokeMESService = invokeMESService;
            _dailySequenceService = dailySequenceService;
            _allocateService = allocateService;
        }
@@ -237,7 +241,7 @@
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{pickingRecord.PickQuantity}");
                return WebResponseContent.Instance.OK($"取消分拣成功");
            }
            catch (Exception ex)
            {
@@ -293,14 +297,15 @@
                _unitOfWorkManage.CommitTran();
                // åˆ›å»ºå›žåº“任务
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, TaskTypeEnum.InPick);
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, TaskTypeEnum.InPick, task.PalletType);
                // æ›´æ–°è®¢å•状态(不触发MES回传)
                await UpdateOrderStatusForReturn(orderNo);
                return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{statusAnalysis.TotalReturnQty}");
                return WebResponseContent.Instance.OK($"回库操作成功");
                //return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{statusAnalysis.TotalReturnQty}");
            }
            catch (Exception ex)
            {
@@ -616,7 +621,7 @@
            result.FinalBarcode = newBarcode;
            result.SplitResults.AddRange(CreateSplitResults(lockInfo, actualQty, remainingStockQty, newBarcode, stockDetail.Barcode));
            _logger.LogInformation($"拆包分拣完成 - OrderDetailId: {lockInfo.OrderDetailId}, åˆ†æ‹£æ•°é‡: {actualQty}");
        }
@@ -737,7 +742,73 @@
        #endregion
        #region å–消分拣私有方法
        private async Task<ValidationResult<bool>> ValidateDataConsistencyBeforeCancel(CancelPickingContext context)
        {
            try
            {
                // 1. éªŒè¯è®¢å•明细数据
                var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == context.OrderDetail.Id);
                if (currentOrderDetail.OverOutQuantity < context.PickingRecord.PickQuantity)
                    return ValidationResult<bool>.Error($"订单明细已出库数量({currentOrderDetail.OverOutQuantity})小于取消数量({context.PickingRecord.PickQuantity})");
                if (currentOrderDetail.PickedQty < context.PickingRecord.PickQuantity)
                    return ValidationResult<bool>.Error($"订单明细已拣选数量({currentOrderDetail.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})");
                // 2. éªŒè¯é”å®šä¿¡æ¯æ•°æ®
                var currentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .FirstAsync(x => x.Id == context.LockInfo.Id);
                if (currentLockInfo.PickedQty < context.PickingRecord.PickQuantity)
                    return ValidationResult<bool>.Error($"锁定信息已拣选数量({currentLockInfo.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})");
                // 3. éªŒè¯åº“存数据
                var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == context.PickingRecord.Barcode && x.StockId == context.PickingRecord.StockId);
                if (currentStockDetail == null)
                    return ValidationResult<bool>.Error($"未找到对应的库存明细记录");
                if (currentStockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                    currentStockDetail.Status == StockStatusEmun.入库完成.ObjToInt())
                    return ValidationResult<bool>.Error($"条码{context.PickingRecord.Barcode}已经回库,无法取消分拣");
                // 4. éªŒè¯çŠ¶æ€æµè½¬çš„åˆæ³•æ€§
                if (!await CanCancelPicking(currentLockInfo, currentStockDetail))
                    return ValidationResult<bool>.Error($"当前状态不允许取消分拣");
                return ValidationResult<bool>.Success(true);
            }
            catch (Exception ex)
            {
                _logger.LogError($"取消分拣数据一致性验证失败: {ex.Message}");
                return ValidationResult<bool>.Error($"数据验证失败: {ex.Message}");
            }
        }
        private async Task<bool> CanCancelPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail)
        {
            // é”å®šä¿¡æ¯çŠ¶æ€æ£€æŸ¥
            if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                return false;
            // åº“存状态检查
            if (stockDetail.Status == StockStatusEmun.出库完成.ObjToInt())
                return false;
            // å¦‚果是拆包记录,还需要检查父锁定信息状态
            if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
            {
                var parentLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .FirstAsync(x => x.Id == lockInfo.ParentLockId.Value);
                if (parentLock == null || parentLock.Status == (int)OutLockStockStatusEnum.回库中)
                    return false;
            }
            return true;
        }
        private async Task<ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>> ValidateCancelRequest(string orderNo, string palletCode, string barcode)
        {
            // åŸºç¡€å‚数验证
@@ -845,30 +916,25 @@
                   stockDetail.Status == StockStatusEmun.入库完成.ObjToInt();
        }
        private async Task ExecuteCancelLogic(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord,
            Dt_OutboundOrderDetail orderDetail, string orderNo)
        Dt_OutboundOrderDetail orderDetail, string orderNo)
        {
            decimal cancelQty = pickingRecord.PickQuantity;
            var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
        .Where(it => it.Barcode == pickingRecord.Barcode && it.StockId == pickingRecord.StockId)
        .FirstAsync();
            if (currentStockDetail != null &&
                (currentStockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                 currentStockDetail.Status == StockStatusEmun.入库完成.ObjToInt()))
            // 1. æ•°æ®ä¸€è‡´æ€§éªŒè¯
            var context = new CancelPickingContext
            {
                throw new Exception($"条码{pickingRecord.Barcode}已经回库,无法取消分拣");
            }
            //   æ£€æŸ¥å–消后数量不会为负数
            decimal newOverOutQuantity = orderDetail.OverOutQuantity - cancelQty;
            decimal newPickedQty = orderDetail.PickedQty - cancelQty;
                LockInfo = lockInfo,
                PickingRecord = pickingRecord,
                OrderDetail = orderDetail,
                OrderNo = orderNo,
                CancelQuantity = cancelQty
            };
            if (newOverOutQuantity < 0 || newPickedQty < 0)
            {
                throw new Exception($"取消分拣将导致数据异常:已出库{newOverOutQuantity},已拣选{newPickedQty}");
            }
            var validationResult = await ValidateDataConsistencyBeforeCancel(context);
            if (!validationResult.IsValid)
                throw new Exception(validationResult.ErrorMessage);
            //  å¤„理不同类型的取消
            // 2. å¤„理不同类型的取消
            if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
            {
                await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
@@ -878,16 +944,23 @@
                await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty);
            }
            // æ›´æ–°è®¢å•明细
            //  æ›´æ–°è®¢å•明细
            await UpdateOrderDetailOnCancel(pickingRecord.OrderDetailId, cancelQty);
            //  åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>()
            var deleteResult = await Db.Deleteable<Dt_PickingRecord>()
                .Where(x => x.Id == pickingRecord.Id)
                .ExecuteCommandAsync();
            //  é‡æ–°æ£€æŸ¥è®¢å•状态
            if (deleteResult <= 0)
                throw new Exception("删除拣选记录失败");
            _logger.LogInformation($"删除拣选记录 - è®°å½•ID: {pickingRecord.Id}, æ¡ç : {pickingRecord.Barcode}");
            // é‡æ–°æ£€æŸ¥è®¢å•状态
            await UpdateOrderStatusForReturn(orderNo);
        }
        private async Task HandleSplitBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
@@ -900,61 +973,85 @@
            if (parentLockInfo == null)
                throw new Exception("未找到父锁定信息,无法取消拆包分拣");
            // æ£€æŸ¥çˆ¶æ¡ç å’Œæ‹†åŒ…条码的状态
            if (await IsLockInfoReturned(parentLockInfo))
            {
                throw new Exception($"父条码{parentLockInfo.CurrentBarcode}已经回库,无法取消拆包分拣");
            }
            if (await IsLockInfoReturned(lockInfo))
            {
                throw new Exception($"拆包条码{lockInfo.CurrentBarcode}已经回库,无法取消拆包分拣");
            }
            // æ¢å¤çˆ¶é”å®šä¿¡æ¯çš„分配数量
            parentLockInfo.AssignQuantity += cancelQty;
            parentLockInfo.Status = (int)OutLockStockStatusEnum.出库中; // æ¢å¤ä¸ºå‡ºåº“中状态
            await _outStockLockInfoService.Db.Updateable(parentLockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
            // æ¢å¤çˆ¶æ¡ç åº“å­˜
            var parentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == parentLockInfo.CurrentBarcode && x.StockId == parentLockInfo.StockId)
                .FirstAsync();
            if (stockDetail != null)
            if (parentStockDetail != null)
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity = stockDetail.StockQuantity;
                stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                parentStockDetail.StockQuantity += cancelQty;
                parentStockDetail.OutboundQuantity = parentStockDetail.StockQuantity;
                parentStockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
                await _stockInfoDetailService.Db.Updateable(parentStockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"恢复父条码库存 - æ¡ç : {parentStockDetail.Barcode}, æ¢å¤æ•°é‡: {cancelQty}, æ–°åº“å­˜: {parentStockDetail.StockQuantity}");
            }
            // å¤„理拆包产生的新条码库存
            var splitStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId)
                .FirstAsync();
            if (splitStockDetail != null)
            {
                // åˆ é™¤æ‹†åŒ…产生的新条码库存记录
                await _stockInfoDetailService.Db.Deleteable(splitStockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"删除拆包新条码库存 - æ¡ç : {splitStockDetail.Barcode}");
            }
            // æ›´æ–°æ‹†åŒ…记录状态
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
            var updateCount = await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已撤销,
                    IsReverted = true,
                    Operator = App.User.UserName,
                    RevertTime = DateTime.Now
                })
                .Where(x => x.NewBarcode == lockInfo.CurrentBarcode && !x.IsReverted)
                .ExecuteCommandAsync();
            _logger.LogInformation($"更新拆包记录状态 - æ›´æ–°è®°å½•æ•°: {updateCount}");
            // åˆ é™¤æ‹†åŒ…产生的锁定信息
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == lockInfo.Id)
                .ExecuteCommandAsync();
            await UpdateOrderDetailOnCancel(pickingRecord.OrderDetailId, cancelQty);
            _logger.LogInformation($"删除拆包锁定信息 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}");
        }
        private async Task HandleNormalBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            if (await IsLockInfoReturned(lockInfo))
            {
                throw new Exception($"条码{lockInfo.CurrentBarcode}已经回库,无法取消分拣");
            }
            // æ¢å¤é”å®šä¿¡æ¯
            lockInfo.PickedQty -= cancelQty;
            if (lockInfo.PickedQty < 0) lockInfo.PickedQty = 0;
            lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            // åªæœ‰å½“拣选数量完全取消时才恢复状态
            if (lockInfo.PickedQty == 0)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            }
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            _logger.LogInformation($"恢复锁定信息 - é”å®šID: {lockInfo.Id}, æ‰£å‡æ‹£é€‰æ•°é‡: {cancelQty}, æ–°å·²æ‹£é€‰æ•°é‡: {lockInfo.PickedQty}");
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
@@ -965,36 +1062,57 @@
            {
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity = stockDetail.StockQuantity;
                stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
                // æ¢å¤åº“存状态
                if (stockDetail.Status == StockStatusEmun.出库完成.ObjToInt())
                {
                    stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
                }
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                _logger.LogInformation($"恢复库存 - æ¡ç : {stockDetail.Barcode}, æ¢å¤æ•°é‡: {cancelQty}, " +
                                      $"新库存: {stockDetail.StockQuantity}, æ–°çŠ¶æ€: {stockDetail.Status}");
            }
            else
            {
                _logger.LogWarning($"未找到库存记录 - æ¡ç : {pickingRecord.Barcode}, åº“å­˜ID: {pickingRecord.StockId}");
            }
        }
        private async Task UpdateOrderDetailOnCancel(int orderDetailId, decimal cancelQty)
        {
            // èŽ·å–æœ€æ–°çš„è®¢å•æ˜Žç»†æ•°æ®
            // èŽ·å–æœ€æ–°çš„è®¢å•æ˜Žç»†æ•°æ®ï¼ˆå¸¦é”ï¼‰
            var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .With(SqlWith.RowLock)
                .FirstAsync(x => x.Id == orderDetailId);
            decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity - cancelQty;
            decimal newPickedQty = currentOrderDetail.PickedQty - cancelQty;
            // æ£€æŸ¥å–消后数量不会为负数
            if (newOverOutQuantity < 0 || newPickedQty < 0)
            {
                throw new Exception($"取消分拣将导致已出库数量({newOverOutQuantity})或已拣选数量({newPickedQty})为负数");
            }
            // ä¸¥æ ¼æ£€æŸ¥å–消后数量不会为负数
            if (newOverOutQuantity < 0)
                throw new Exception($"取消分拣将导致已出库数量({newOverOutQuantity})为负数");
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
            if (newPickedQty < 0)
                throw new Exception($"取消分拣将导致已拣选数量({newPickedQty})为负数");
            // æ›´æ–°è®¢å•明细
            var updateResult = await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => new Dt_OutboundOrderDetail
                {
                    PickedQty = newPickedQty,
                    OverOutQuantity = newOverOutQuantity,
                    OverOutQuantity = newOverOutQuantity
                })
                .Where(it => it.Id == orderDetailId)
                .ExecuteCommandAsync();
        }
            if (updateResult <= 0)
                throw new Exception("更新订单明细失败");
            _logger.LogInformation($"更新订单明细 - OrderDetailId: {orderDetailId}, " +
                                  $"扣减已出库: {cancelQty}, æ–°å·²å‡ºåº“: {newOverOutQuantity}, " +
                                  $"扣减已拣选: {cancelQty}, æ–°å·²æ‹£é€‰: {newPickedQty}");
        }
        #endregion
        #region å›žåº“操作私有方法
@@ -1037,7 +1155,7 @@
            return task;
        }
        private async Task<decimal> CalculateSplitReturnQuantity(List<Dt_SplitPackageRecord> splitRecords, int stockId)
        {
            decimal totalQty = 0;
@@ -1045,11 +1163,14 @@
            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)
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockId &&
                           it.Status != StockStatusEmun.出库完成.ObjToInt())
                        .FirstAsync();
                    if (originalStock != null && originalStock.StockQuantity > 0)
@@ -1063,7 +1184,7 @@
                if (!processedBarcodes.Contains(splitRecord.NewBarcode))
                {
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockId)
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockId && it.Status != StockStatusEmun.出库完成.ObjToInt())
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
@@ -1111,11 +1232,13 @@
                else
                {
                    locationtype = stockInfo.LocationType;
                    _stockInfoService.DeleteData(stockInfo);
                }
                var targetAddress = originalTask.TargetAddress;
                await CleanupZeroStockData(stockInfoId);
                var emptystockInfo = new Dt_StockInfo() { PalletType = PalletTypeEnum.Empty.ObjToInt(), StockStatus = StockStatusEmun.组盘暂存.ObjToInt(), PalletCode = palletCode, LocationType = locationtype };
                emptystockInfo.Details = new List<Dt_StockInfoDetail>();
@@ -1123,7 +1246,7 @@
                //空托盘如何处理  è¿˜æœ‰ä¸€ä¸ªå‡ºåº“任务要处理。
                originalTask.PalletType = PalletTypeEnum.Empty.ObjToInt();
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty);
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt());
            }
            catch (Exception ex)
@@ -1143,19 +1266,41 @@
            {
                await HandleRemainingLocksReturn(analysis.RemainingLocks, stockInfo.Id);
               // await UpdateOrderDetailsOnReturn(analysis.RemainingLocks);
            }
                // await UpdateOrderDetailsOnReturn(analysis.RemainingLocks);
            }
            // å¤„理托盘上其他库存货物
            if (analysis.HasPalletStockGoods)
            {
                await HandlePalletStockGoodsReturn(analysis.PalletStockGoods);
                var validStockGoods = analysis.PalletStockGoods
            .Where(x => x.Status != StockStatusEmun.出库完成.ObjToInt())
            .ToList();
                if (validStockGoods.Any())
                {
                    await HandlePalletStockGoodsReturn(analysis.PalletStockGoods);
                }
                else
                {
                    _logger.LogInformation("没有有效的库存货物需要回库");
                }
            }
            // å¤„理拆包记录
            if (analysis.HasSplitRecords)
            {
                await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode);
                var validSplitRecords = analysis.SplitRecords
            .Where(x => x.Status != (int)SplitPackageStatusEnum.已拣选)
            .ToList();
                if (validSplitRecords.Any())
                {
                    await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode);
                }
                else
                {
                    _logger.LogInformation("没有有效的拆包记录需要处理");
                }
            }
            // æ›´æ–°åº“存主表状态
@@ -1179,7 +1324,13 @@
            foreach (var lockInfo in remainingLocks)
            {
                decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                if (returnQty <= 0)
                {
                    _logger.LogWarning($"锁定记录{lockInfo.Id}无需回库,分配数量: {lockInfo.AssignQuantity}, å·²æ‹£é€‰: {lockInfo.PickedQty}");
                    continue;
                }
                _logger.LogInformation($"处理锁定记录回库 - é”å®šID: {lockInfo.Id}, æ¡ç : {lockInfo.CurrentBarcode}, å›žåº“数量: {returnQty}");
                // æŸ¥æ‰¾å¯¹åº”的库存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
@@ -1195,23 +1346,23 @@
                else
                {
                    // åˆ›å»ºæ–°çš„库存记录
                    var newStockDetail = new Dt_StockInfoDetail
                    {
                        StockId = lockInfo.StockId,
                        MaterielCode = lockInfo.MaterielCode,
                        MaterielName = lockInfo.MaterielName,
                        OrderNo = lockInfo.OrderNo,
                        BatchNo = lockInfo.BatchNo,
                        StockQuantity = returnQty,
                        OutboundQuantity = 0,
                        Barcode = lockInfo.CurrentBarcode,
                        InboundOrderRowNo = "",
                        Status = StockStatusEmun.入库确认.ObjToInt(),
                        SupplyCode = lockInfo.SupplyCode,
                        WarehouseCode = lockInfo.WarehouseCode,
                        Unit = lockInfo.Unit,
                    };
                    await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                    //var newStockDetail = new Dt_StockInfoDetail
                    //{
                    //    StockId = lockInfo.StockId,
                    //    MaterielCode = lockInfo.MaterielCode,
                    //    MaterielName = lockInfo.MaterielName,
                    //    OrderNo = lockInfo.OrderNo,
                    //    BatchNo = lockInfo.BatchNo,
                    //    StockQuantity = returnQty,
                    //    OutboundQuantity = 0,
                    //    Barcode = lockInfo.CurrentBarcode,
                    //    InboundOrderRowNo = "",
                    //    Status = StockStatusEmun.入库确认.ObjToInt(),
                    //    SupplyCode = lockInfo.SupplyCode,
                    //    WarehouseCode = lockInfo.WarehouseCode,
                    //    Unit = lockInfo.Unit,
                    //};
                    //await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                }
            }
        }
@@ -1255,23 +1406,41 @@
            {
                _logger.LogInformation($"待回库货物 - æ¡ç : {stockGood.Barcode}, æ•°é‡: {stockGood.StockQuantity}, å½“前状态: {stockGood.Status}");
                // æ¢å¤åº“存状态
                stockGood.OutboundQuantity = 0;
                stockGood.Status = StockStatusEmun.入库确认.ObjToInt();
                if (stockGood.Status != StockStatusEmun.出库完成.ObjToInt())
                {
                    stockGood.OutboundQuantity = 0;
                    stockGood.Status = StockStatusEmun.入库确认.ObjToInt();
                    await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
                await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
                    _logger.LogInformation($"库存货物回库完成 - æ¡ç : {stockGood.Barcode}, æ–°çŠ¶æ€: {stockGood.Status}");
                }
                else
                {
                    _logger.LogWarning($"跳过已出库完成的货物 - æ¡ç : {stockGood.Barcode}");
                }
            }
        }
        private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, string orderNo, string palletCode)
        {
            var validRecords = splitRecords.Where(x => x.Status != (int)SplitPackageStatusEnum.已拣选).ToList();
            if (!validRecords.Any())
            {
                _logger.LogInformation("没有需要回库的拆包记录");
                return;
            }
            _logger.LogInformation($"更新{validRecords.Count}条拆包记录状态为已回库");
            // æ›´æ–°æ‹†åŒ…记录状态
            await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
                .SetColumns(x => new Dt_SplitPackageRecord
                {
                    Status = (int)SplitPackageStatusEnum.已回库,
                    Operator = App.User.UserName
                })
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && !x.IsReverted)
                .Where(x => validRecords.Select(r => r.Id).Contains(x.Id))
                .ExecuteCommandAsync();
        }
@@ -1290,7 +1459,7 @@
        /// <param name="originalTask"></param>
        /// <param name="analysis"></param>
        /// <returns></returns>
        private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, TaskTypeEnum taskTypeEnum)
        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);
@@ -1304,13 +1473,14 @@
                Grade = 0,
                PalletCode = palletCode,
                NextAddress = "",
                OrderNo = originalTask.OrderNo,
                // OrderNo = originalTask.OrderNo,
                OrderNo = orderNo,
                Roadway = newLocation.RoadwayNo,
                SourceAddress = stations[originalTask.TargetAddress],
                TargetAddress = newLocation.LocationCode,
                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                TaskType = taskTypeEnum.ObjToInt(),
                PalletType = originalTask.PalletType,
                PalletType = palletType,
                WarehouseId = originalTask.WarehouseId
            };
@@ -1346,15 +1516,15 @@
                    containerCode = palletCode
                });
                if (moveResult)
                //if (moveResult)
                //{
                // 2. åˆ›å»ºå›žåº“任务
                var essTask = new TaskModel()
                {
                    // 2. åˆ›å»ºå›žåº“任务
                    var essTask = new TaskModel()
                    {
                        taskType = "putaway",
                        taskGroupCode = "",
                        groupPriority = 0,
                        tasks = new List<TasksType>{  new() {
                    taskType = "putaway",
                    taskGroupCode = "",
                    groupPriority = 0,
                    tasks = new List<TasksType>{  new() {
                            taskCode = returnTask.TaskNum.ToString(),
                            taskPriority = 0,
                            taskDescribe = new TaskDescribeType
@@ -1368,11 +1538,11 @@
                                storageTag = ""
                            }
                        } }
                    };
                };
                    var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                    _logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                }
                var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                _logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                //}
            }
            catch (Exception ex)
            {
@@ -1415,15 +1585,19 @@
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .SetColumns(x => new Dt_OutboundOrder
                        {
                            OrderStatus = newStatus,
                            Operator = App.User.UserName,
                        })
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    // åªæœ‰æ­£å¸¸åˆ†æ‹£å®Œæˆæ—¶æ‰å‘MES反馈
                    if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
                    {
                        await HandleOrderCompletion(outboundOrder, orderNo);
                    }
                    //if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
                    //{
                    //    await HandleOrderCompletion(outboundOrder, orderNo);
                    //}
                }
            }
            catch (Exception ex)
@@ -1432,7 +1606,7 @@
            }
        }
        private async Task UpdateOrderStatusForReturn(string orderNo)
        {
@@ -1464,7 +1638,11 @@
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                          .SetColumns(x => new Dt_OutboundOrder
                          {
                              OrderStatus = newStatus,
                              Operator = App.User.UserName,
                          })
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
@@ -1480,73 +1658,141 @@
        private async Task HandleOrderCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        {
            // è°ƒæ‹¨å‡ºåº“和重检出库不需要反馈MES
            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt() ||
                outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt())
            {
                return;
            }
            try
            {
                var feedmodel = new FeedbackOutboundRequestModel
                var allocate = _allocateService.Repository.QueryData(x => x.UpperOrderNo == outboundOrder.UpperOrderNo).First();
                var feedmodel = new AllocateDto
                {
                    reqCode = Guid.NewGuid().ToString(),
                    reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    business_type = outboundOrder.BusinessType,
                    factoryArea = outboundOrder.FactoryArea,
                    operationType = 1,
                    ReqCode = Guid.NewGuid().ToString(),
                    ReqTime = DateTime.Now.ToString(),
                    BusinessType = "3",
                    FactoryArea = outboundOrder.FactoryArea,
                    OperationType = 1,
                    Operator = App.User.UserName,
                    orderNo = outboundOrder.UpperOrderNo,
                    documentsNO = outboundOrder.OrderNo,
                    status = outboundOrder.OrderStatus,
                    details = new List<FeedbackOutboundDetailsModel>()
                };
                    OrderNo = outboundOrder.UpperOrderNo,
                   // documentsNO = outboundOrder.OrderNo,
                   // status = outboundOrder.OrderStatus,
                    fromWarehouse = allocate?.FromWarehouse ?? "",
                    toWarehouse = allocate?.ToWarehouse ?? "",
                    Details = new List<AllocateDtoDetail>()
                };
                // åªèŽ·å–å·²æ‹£é€‰å®Œæˆçš„é”å®šè®°å½•
                var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成)
                    .ToListAsync();
                var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                   .Select(group => new FeedbackOutboundDetailsModel
                   .Select(group => new AllocateDtoDetail
                   {
                       materialCode = group.Key.MaterielCode,
                       lineNo = group.Key.lineNo,
                       warehouseCode = group.Key.WarehouseCode,
                       qty = group.Sum(x => x.PickedQty),
                       currentDeliveryQty = group.Sum(x => x.PickedQty),
                       unit = group.Key.Unit,
                       barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                       MaterialCode = group.Key.MaterielCode,
                       LineNo = group.Key.lineNo,
                       WarehouseCode = group.Key.WarehouseCode,
                       Qty = group.Sum(x => x.PickedQty),
                       Unit = group.Key.Unit,
                       Barcodes = group.Select(row => new BarcodeInfo
                       {
                           barcode = row.CurrentBarcode,
                           supplyCode = row.SupplyCode,
                           batchNo = row.BatchNo,
                           unit = row.Unit,
                           qty = row.PickedQty
                           Barcode = row.CurrentBarcode,
                           SupplyCode = row.SupplyCode,
                           BatchNo = row.BatchNo,
                           Unit = row.Unit,
                           Qty = row.PickedQty
                       }).ToList()
                   }).ToList();
                feedmodel.Details = groupedData;
                feedmodel.details = groupedData;
                var result = await _invokeMESService.FeedbackOutbound(feedmodel);
                var result = await _invokeMESService.FeedbackAllocate(feedmodel);
                if (result != null && result.code == 200)
                {
                {
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                        .SetColumns(x => x.ReturnToMESStatus == 1)
                        .Where(x => x.OrderId == outboundOrder.Id)
                        .ExecuteCommandAsync();
                           .SetColumns(x => x.ReturnToMESStatus == 1)
                           .Where(x => x.OrderId == outboundOrder.Id).ExecuteCommandAsync();
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.ReturnToMESStatus == 1)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                          .SetColumns(x => new Dt_OutboundOrder
                          {
                              ReturnToMESStatus = 1,
                              Operator = App.User.UserName,
                          }).Where(x => x.OrderNo == orderNo).ExecuteCommandAsync();
                }
                _logger.LogError($"FeedbackOutbound成功 - OrderNo: {orderNo}, {JsonSerializer.Serialize(result)}");
            }
            catch (Exception ex)
            else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
            {
                _logger.LogError($"FeedbackOutbound失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
            else
            {
                try
                {
                    var feedmodel = new FeedbackOutboundRequestModel
                    {
                        reqCode = Guid.NewGuid().ToString(),
                        reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                        business_type = outboundOrder.BusinessType,
                        factoryArea = outboundOrder.FactoryArea,
                        operationType = 1,
                        Operator = App.User.UserName,
                        orderNo = outboundOrder.UpperOrderNo,
                        documentsNO = outboundOrder.OrderNo,
                        status = outboundOrder.OrderStatus,
                        details = new List<FeedbackOutboundDetailsModel>()
                    };
                    // åªèŽ·å–å·²æ‹£é€‰å®Œæˆçš„é”å®šè®°å½•
                    var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成)
                        .ToListAsync();
                    var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                       .Select(group => new FeedbackOutboundDetailsModel
                       {
                           materialCode = group.Key.MaterielCode,
                           lineNo = group.Key.lineNo,
                           warehouseCode = group.Key.WarehouseCode,
                           qty = group.Sum(x => x.PickedQty),
                           currentDeliveryQty = group.Sum(x => x.PickedQty),
                           unit = group.Key.Unit,
                           barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                           {
                               barcode = row.CurrentBarcode,
                               supplyCode = row.SupplyCode,
                               batchNo = row.BatchNo,
                               unit = row.Unit,
                               qty = row.PickedQty
                           }).ToList()
                       }).ToList();
                    feedmodel.details = groupedData;
                    var result = await _invokeMESService.FeedbackOutbound(feedmodel);
                    if (result != null && result.code == 200)
                    {
                        await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                            .SetColumns(x => x.ReturnToMESStatus == 1)
                            .Where(x => x.OrderId == outboundOrder.Id)
                            .ExecuteCommandAsync();
                        await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                              .SetColumns(x => new Dt_OutboundOrder
                              {
                                  ReturnToMESStatus = 1,
                                  Operator = App.User.UserName,
                              })
                            .Where(x => x.OrderNo == orderNo)
                            .ExecuteCommandAsync();
                    }
                    _logger.LogError($"FeedbackOutbound成功 - OrderNo: {orderNo}, {JsonSerializer.Serialize(result)}");
                }
                catch (Exception ex)
                {
                    _logger.LogError($"FeedbackOutbound失败 - OrderNo: {orderNo}, Error: {ex.Message}");
                }
            }
        }
@@ -1679,6 +1925,7 @@
            if (outboundOrder != null && allCompleted && outboundOrder.OrderStatus != (int)OutOrderStatusEnum.出库完成)
            {
                outboundOrder.OrderStatus = (int)OutOrderStatusEnum.出库完成;
                outboundOrder.Operator = App.User.UserName;
                await _outboundOrderService.Db.Updateable(outboundOrder).ExecuteCommandAsync();
                _logger.LogInformation($"订单 {orderNo} å·²æ ‡è®°ä¸ºå‡ºåº“完成");
@@ -1728,7 +1975,7 @@
                StockId = stockId
            };
            // 1. åˆ†æžæœªåˆ†æ‹£çš„出库锁定记录
            // åˆ†æžæœªåˆ†æ‹£çš„出库锁定记录
            var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
@@ -1740,9 +1987,10 @@
                result.HasRemainingLocks = true;
                result.RemainingLocks = remainingLocks;
                result.RemainingLocksReturnQty = remainingLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                _logger.LogInformation($"发现{remainingLocks.Count}条未分拣锁定记录,总数量: {result.RemainingLocksReturnQty}");
            }
            // 2. åˆ†æžæ‰˜ç›˜ä¸Šçš„库存货物
            // åˆ†æžæ‰˜ç›˜ä¸Šçš„库存货物
            var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.StockId == stockId &&
                     (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
@@ -1756,13 +2004,21 @@
                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)}");
                }
            }
            // 3. åˆ†æžæ‹†åŒ…记录
            //分析拆包记录
            var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
                           !it.IsReverted &&
                           !it.IsReverted && it.Status != (int)SplitPackageStatusEnum.已拣选 &&
                           it.Status != (int)SplitPackageStatusEnum.已回库)
                .ToListAsync();
@@ -1771,6 +2027,8 @@
                result.HasSplitRecords = true;
                result.SplitRecords = splitRecords;
                result.SplitReturnQty = await CalculateSplitReturnQuantity(splitRecords, stockId);
                _logger.LogInformation($"发现{splitRecords.Count}条未拣选拆包记录,总数量: {result.SplitReturnQty}");
            }
            // 4. è®¡ç®—总回库数量和空托盘状态
@@ -1784,6 +2042,10 @@
                           x.PalletCode == palletCode &&
                           x.TaskStatus == (int)TaskStatusEnum.New)
                .AnyAsync();
            _logger.LogInformation($"托盘状态分析完成 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}, " +
                                  $"总回库数量: {result.TotalReturnQty}, æ˜¯å¦ç©ºæ‰˜ç›˜: {result.IsEmptyPallet}, " +
                                  $"有进行中任务: {result.HasActiveTasks}");
            return result;
        }
@@ -2091,5 +2353,29 @@
        public bool CanReturn => HasItemsToReturn && !HasActiveTasks;
        public bool CanRemove => IsEmptyPallet && !HasActiveTasks;
    }
    public class PickingContext
    {
        public string OrderNo { get; set; }
        public string PalletCode { get; set; }
        public string Barcode { get; set; }
        public string Operator { get; set; }
        public Dt_OutStockLockInfo LockInfo { get; set; }
        public Dt_OutboundOrderDetail OrderDetail { get; set; }
        public Dt_StockInfoDetail StockDetail { get; set; }
        public decimal ActualQuantity { get; set; }
        public string AdjustedReason { get; set; }
    }
    public class CancelPickingContext
    {
        public string OrderNo { get; set; }
        public string PalletCode { get; set; }
        public string Barcode { get; set; }
        public string Operator { get; set; }
        public decimal CancelQuantity { get; set; }
        public Dt_PickingRecord PickingRecord { get; set; }
        public Dt_OutStockLockInfo LockInfo { get; set; }
        public Dt_OutboundOrderDetail OrderDetail { get; set; }
    }
    #endregion
}