pan
2025-11-30 c100108c13c74ebd99253c4c144b262720b55ecd
提交
已修改8个文件
1080 ■■■■ 文件已修改
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-shm 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-shm 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/BatchOutBoundDto.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundOrderDetail.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundBatchPickingService.cs 1071 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-shm
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-shm
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/BatchOutBoundDto.cs
@@ -186,6 +186,8 @@
        public decimal SplitQuantity { get; set; }
        public string Operator { get; set; }
        public bool IsReverted { get; set; }
        public bool IsAutoSplit { get; set; }
    }
    #endregion
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundOrderDetail.cs
@@ -148,7 +148,7 @@
        public decimal AllocatedQuantity { get; set; }
        public string BatchAllocateStatus { get; set; }
        public int BatchAllocateStatus { get; set; }
        /// <summary>
        /// è™šæ‹Ÿå‡ºå…¥åº“数量
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs
@@ -133,6 +133,8 @@
        public string PalletCode { get; set; }
        public int StockId { get; set; }
        public bool IsReverted { get; set; } = false;
        public bool IsAutoSplit { get; set; } = false;
        public int OutStockLockInfoId { get; set; } // å…³è”的出库锁定信息
        public string OriginalBarcode { get; set; } // åŽŸæ¡ç 
        public string NewBarcode { get; set; } // æ–°æ¡ç 
ÏîÄ¿´úÂë/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;
@@ -267,7 +268,7 @@
                await UpdateOrderStatusAfterPalletRemoval(orderNo);
                // è®°å½•操作历史
                await RecordEmptyPalletRemoval(orderNo, palletCode, completedLocks);
               // await RecordEmptyPalletRemoval(orderNo, palletCode, completedLocks);
                _unitOfWorkManage.CommitTran();
@@ -411,32 +412,68 @@
            {
                _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);
                    // é‡è¦ä¿®å¤ï¼šè‡ªåŠ¨æ‹†åŒ…åŽï¼Œç«‹å³æ‰§è¡ŒåŽŸæ¡ç çš„åˆ†æ‹£
                    var actualPickedQty = lockInfo.AssignQuantity;
                    var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
                // 4. è®°å½•拣选历史
                await RecordPickingHistory(pickingResult, orderNo, palletCode);
                    // æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据
                    await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo);
                    // è®°å½•拣选历史
                    await RecordPickingHistory(pickingResult, orderNo, palletCode);
                    _unitOfWorkManage.CommitTran();
                    return WebResponseContent.Instance.OK("自动拆包并分拣成功", new
                    {
                        AutoSplitted = true,
                        NewBarcode = autoSplitResult.NewBarcode,
                        OriginalBarcode = barcode,
                        SplitQuantity = autoSplitResult.SplitQuantity,
                        PickedQuantity = actualPickedQty,
                        MaterialCode = lockInfo.MaterielCode
                    });
                }
                // æ­£å¸¸åˆ†æ‹£æµç¨‹ï¼ˆä¸éœ€è¦è‡ªåŠ¨æ‹†åŒ…ï¼‰
                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)
@@ -494,6 +531,8 @@
        #region æ‰‹åŠ¨æ‹†åŒ…
        #region æ‰‹åŠ¨æ‹†åŒ…ç›¸å…³æ–¹æ³•
        /// <summary>
        /// æ‰‹åŠ¨æ‹†åŒ…
        /// </summary>
@@ -503,15 +542,15 @@
            {
                _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();
@@ -530,9 +569,224 @@
            }
        }
        /// <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<SplitResultDto> ExecuteManualSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal splitQuantity, string palletCode)
        {
            _logger.LogInformation($"开始执行手动拆包逻辑 - åŽŸæ¡ç : {stockDetail.Barcode}, æ‹†åŒ…数量: {splitQuantity}");
            // é‡è¦ä¿®å¤ï¼šèŽ·å–è®¢å•æ˜Žç»†å¹¶éªŒè¯åˆ†é…æ•°é‡
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
            if (orderDetail == null)
                throw new InvalidOperationException("未找到订单明细");
            // éªŒè¯æ‹†åŒ…数量不会导致分配数量为负数
            if (lockInfo.AssignQuantity < splitQuantity)
            {
                throw new InvalidOperationException($"拆包数量超过锁定信息分配数量,拆包数量: {splitQuantity}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}");
            }
            // ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
            // è®°å½•拆包前的分配数量
            decimal originalAssignQtyBefore = lockInfo.AssignQuantity;
            decimal originalOrderQtyBefore = lockInfo.OrderQuantity;
            // 1. åˆ›å»ºæ–°åº“存明细
            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();
            _logger.LogInformation($"创建新库存明细 - æ¡ç : {newBarcode}, åº“存数量: {splitQuantity}");
            // 2. æ›´æ–°åŽŸåº“å­˜æ˜Žç»†
            decimal originalStockQtyBefore = stockDetail.StockQuantity;
            stockDetail.StockQuantity -= splitQuantity;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            _logger.LogInformation($"更新原库存明细 - åº“存数量从 {originalStockQtyBefore} å‡å°‘到 {stockDetail.StockQuantity}");
            // 3. åˆ›å»ºæ–°é”å®šä¿¡æ¯
            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();
            _logger.LogInformation($"创建新锁定信息 - æ¡ç : {newBarcode}, åˆ†é…æ•°é‡: {splitQuantity}");
            // 4. æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity -= splitQuantity;
            lockInfo.OrderQuantity -= splitQuantity;
            _logger.LogInformation($"更新原锁定信息 - åˆ†é…æ•°é‡ä»Ž {originalAssignQtyBefore} å‡å°‘到 {lockInfo.AssignQuantity}");
            _logger.LogInformation($"更新原锁定信息 - è®¢å•数量从 {originalOrderQtyBefore} å‡å°‘到 {lockInfo.OrderQuantity}");
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // é‡è¦ä¿®å¤ï¼šæ‰‹åŠ¨æ‹†åŒ…ä¸ä¼šæ”¹å˜è®¢å•æ˜Žç»†çš„åˆ†é…æ•°é‡ï¼Œå› ä¸ºæ€»åˆ†é…æ•°é‡ä¸å˜
            // åªæ˜¯ä»Žä¸€ä¸ªé”å®šä¿¡æ¯è½¬ç§»åˆ°å¦ä¸€ä¸ªé”å®šä¿¡æ¯
            _logger.LogInformation($"订单明细分配数量保持不变 - å·²åˆ†é…æ•°é‡: {orderDetail.AllocatedQuantity}");
            // 5. è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, false);
            _logger.LogInformation($"手动拆包逻辑执行完成");
            return new SplitResultDto { NewBarcode = newBarcode };
        }
        #endregion
        #endregion
        #region å–消拆包 
@@ -588,15 +842,24 @@
            Dt_OutStockLockInfo originalLockInfo, Dt_OutStockLockInfo newLockInfo,
            Dt_StockInfoDetail newStockDetail)
        {
            // 1. æ¢å¤åŽŸé”å®šä¿¡æ¯
            // æ³¨æ„ï¼šè¿™é‡Œéœ€è¦ç´¯åŠ ï¼Œè€Œä¸æ˜¯ç®€å•çš„èµ‹å€¼ï¼Œå› ä¸ºå¯èƒ½æœ‰å¤šæ¬¡æ‹†åŒ…
            _logger.LogInformation($"开始执行取消拆包逻辑 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}, æ‹†åŒ…数量: {splitRecord.SplitQty}");
            // æ¢å¤åŽŸé”å®šä¿¡æ¯
            decimal originalAssignQtyBefore = originalLockInfo.AssignQuantity;
            decimal originalOrderQtyBefore = originalLockInfo.OrderQuantity;
            // é‡è¦ä¿®å¤ï¼šæ¢å¤åˆ†é…æ•°é‡å’Œè®¢å•数量
            originalLockInfo.AssignQuantity += splitRecord.SplitQty;
            originalLockInfo.OrderQuantity += splitRecord.SplitQty;
            _logger.LogInformation($"恢复原锁定信息 - åˆ†é…æ•°é‡ä»Ž {originalAssignQtyBefore} å¢žåŠ åˆ° {originalLockInfo.AssignQuantity}");
            _logger.LogInformation($"恢复原锁定信息 - è®¢å•数量从 {originalOrderQtyBefore} å¢žåŠ åˆ° {originalLockInfo.OrderQuantity}");
            // å¦‚果原锁定信息的状态是拣选完成,需要重新设置为出库中
            if (originalLockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
            {
                originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中;
                _logger.LogInformation($"原锁定信息状态从拣选完成恢复为出库中");
            }
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
@@ -605,35 +868,47 @@
            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.出库锁定;
                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();
            //标记拆包记录为已撤销
            // æ ‡è®°æ‹†åŒ…记录为已撤销
            splitRecord.IsReverted = true;
            splitRecord.RevertTime = DateTime.Now;
            splitRecord.RevertOperator = App.User.UserName;
            await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
            _logger.LogInformation($"标记拆包记录为已撤销");
            // æ£€æŸ¥å¹¶æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•状态
            await CheckAndUpdateBatchStatus(originalLockInfo.BatchNo);
            await CheckAndUpdateOrderStatus(originalLockInfo.OrderNo);
            _logger.LogInformation($"取消拆包逻辑执行完成");
        }
        /// <summary>
@@ -642,6 +917,8 @@
        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 +928,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 +941,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 +979,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 +1014,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 +1051,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 +1222,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 +1257,8 @@
            // æ‰§è¡Œå–消逻辑
            await ExecuteCancelSplitLogic(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
            _logger.LogInformation($"取消单个拆包记录完成 - åŽŸæ¡ç : {splitRecord.OriginalBarcode}, æ–°æ¡ç : {splitRecord.NewBarcode}");
        }
        #endregion
@@ -810,7 +1284,8 @@
                        NewBarcode = x.NewBarcode,
                        SplitQuantity = x.SplitQty,
                        Operator = x.Operator,
                        IsReverted = x.IsReverted
                        IsReverted = x.IsReverted ,
                        IsAutoSplit = x.IsAutoSplit
                    }).ToList()
                };
@@ -899,9 +1374,7 @@
        #endregion
        #region åˆ†æ‰¹å›žåº“
        /// <summary>
@@ -961,6 +1434,8 @@
        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 +1447,8 @@
            if (lockInfo == null)
                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 +1457,106 @@
            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 (orderDetail.AllocatedQuantity < remainingToPick)
            {
                _logger.LogWarning($"订单明细已分配数量不足 - éœ€è¦æ‹£é€‰: {remainingToPick}, å¯ç”¨åˆ†é…æ•°é‡: {orderDetail.AllocatedQuantity}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
                    $"订单明细分配数量不足,需要拣选:{remainingToPick},可用分配数量:{orderDetail.AllocatedQuantity}");
            }
            // é‡è¦ä¿®å¤ï¼šæ£€æŸ¥é”å®šæ•°é‡æ˜¯å¦è¶³å¤Ÿ
            if (orderDetail.LockQuantity < remainingToPick)
            {
                _logger.LogWarning($"订单明细锁定数量不足 - éœ€è¦æ‹£é€‰: {remainingToPick}, å¯ç”¨é”å®šæ•°é‡: {orderDetail.LockQuantity}");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
                    $"订单明细锁定数量不足,需要拣选:{remainingToPick},可用锁定数量:{orderDetail.LockQuantity}");
            }
            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<AutoSplitResult> 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();
            // æ£€æŸ¥æ˜¯å¦éœ€è¦è‡ªåŠ¨æ‹†åŒ…çš„æ¡ä»¶ï¼š
            // 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));
        }
        #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)
            return new AutoSplitResult
            {
                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
                NewBarcode = splitResult.NewBarcode,
                SplitQuantity = splitQuantity
            };
        }
        private async Task<RevertPickingResult> RevertPickingData(Dt_PickingRecord pickingRecord)
        /// <summary>
        /// æ‰§è¡Œè‡ªåŠ¨æ‹†åŒ…é€»è¾‘
        /// </summary>
        private async Task<SplitResultDto> ExecuteAutoSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal splitQuantity, string palletCode)
        {
            // æ¢å¤é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.Id == pickingRecord.OutStockLockId);
            _logger.LogInformation($"开始执行自动拆包逻辑 - åŽŸæ¡ç : {stockDetail.Barcode}, æ‹†åŒ…数量: {splitQuantity}");
            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,
     decimal splitQuantity, string palletCode)
        {
            // ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
            // åˆ›å»ºæ–°åº“存明细
            // é‡è¦ä¿®å¤ï¼šè®°å½•拆包前的分配数量
            decimal originalAssignQtyBefore = lockInfo.AssignQuantity;
            decimal originalOrderQtyBefore = lockInfo.OrderQuantity;
            // 1. åˆ›å»ºæ–°åº“存明细(剩余部分)
            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,
@@ -1115,20 +1566,25 @@
                BarcodeQty = stockDetail.BarcodeQty,
                BarcodeUnit = stockDetail.BarcodeUnit,
                BusinessType = stockDetail.BusinessType,
                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
            };
            await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
            _logger.LogInformation($"创建新库存明细 - æ¡ç : {newBarcode}, åº“存数量: {splitQuantity}");
            // æ›´æ–°åŽŸåº“å­˜æ˜Žç»†
            stockDetail.StockQuantity -= splitQuantity;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. æ›´æ–°åŽŸåº“å­˜æ˜Žç»†
            // é‡è¦ä¿®å¤ï¼šåŽŸåº“å­˜æ•°é‡è®¾ç½®ä¸ºåˆ†é…æ•°é‡ï¼ˆä¿æŒä¸å˜ï¼‰
            decimal originalStockQtyBefore = stockDetail.StockQuantity;
            // æ³¨æ„ï¼šè‡ªåŠ¨æ‹†åŒ…æ—¶ï¼ŒåŽŸåº“å­˜æ•°é‡ä¿æŒä¸å˜ï¼Œå› ä¸ºæˆ‘ä»¬è¦åˆ†æ‹£çš„å°±æ˜¯åˆ†é…æ•°é‡
            // stockDetail.StockQuantity ä¿æŒä¸å˜
            // åˆ›å»ºæ–°é”å®šä¿¡æ¯ - ä½¿ç”¨æ­£ç¡®çš„ OutboundBatchNo
            _logger.LogInformation($"原库存明细保持不变 - åº“存数量: {stockDetail.StockQuantity}");
            // 3. åˆ›å»ºæ–°é”å®šä¿¡æ¯ï¼ˆå‰©ä½™éƒ¨åˆ†ï¼‰
            var newLockInfo = new Dt_OutStockLockInfo
            {
                OrderNo = lockInfo.OrderNo,
                OrderDetailId = lockInfo.OrderDetailId,
                OutboundBatchNo = lockInfo.OutboundBatchNo, // ä½¿ç”¨ OutboundBatchNo
                OutboundBatchNo = lockInfo.OutboundBatchNo,
                MaterielCode = lockInfo.MaterielCode,
                MaterielName = lockInfo.MaterielName,
                StockId = lockInfo.StockId,
@@ -1150,111 +1606,310 @@
                lineNo = lockInfo.lineNo,
                WarehouseCode = lockInfo.WarehouseCode,
                BarcodeQty = lockInfo.BarcodeQty,
                BarcodeUnit = lockInfo.BarcodeUnit,
                BarcodeUnit = lockInfo.BarcodeUnit,
            };
            await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
            _logger.LogInformation($"创建新锁定信息 - æ¡ç : {newBarcode}, åˆ†é…æ•°é‡: {splitQuantity}");
            // æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity -= splitQuantity;
            lockInfo.OrderQuantity -= splitQuantity;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 4. é‡è¦ä¿®å¤ï¼šè‡ªåŠ¨æ‹†åŒ…æ—¶ï¼ŒåŽŸé”å®šä¿¡æ¯çš„åˆ†é…æ•°é‡ä¿æŒä¸å˜
            // å› ä¸ºè‡ªåŠ¨æ‹†åŒ…åŽï¼ŒåŽŸæ¡ç ä»ç„¶éœ€è¦è¢«åˆ†æ‹£ï¼Œåˆ†é…æ•°é‡ä¸åº”è¯¥æ”¹å˜
            _logger.LogInformation($"原锁定信息保持不变 - åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}, è®¢å•数量: {lockInfo.OrderQuantity}");
            // è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode);
            // 5. è®°å½•拆包历史
            await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, true, originalStockQtyBefore);
            _logger.LogInformation($"自动拆包逻辑执行完成");
            return new SplitResultDto { NewBarcode = newBarcode };
        }
        private async Task ExecuteCancelSplitLogic(Dt_SplitPackageRecord splitRecord, Dt_OutStockLockInfo newLockInfo, Dt_StockInfoDetail newStockDetail)
        #endregion
        #region æ ¸å¿ƒé€»è¾‘方法
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
            Dt_StockInfoDetail stockDetail, decimal actualPickedQty)
        {
            // æ¢å¤åŽŸåº“å­˜
            var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
            _logger.LogInformation($"开始执行分拣逻辑 - æ¡ç : {stockDetail.Barcode}, åˆ†é…æ•°é‡: {lockInfo.AssignQuantity}, å®žé™…拣选: {actualPickedQty}");
            originalStock.StockQuantity += splitRecord.SplitQty;
            await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
            // é‡è¦ä¿®å¤ï¼šå†æ¬¡éªŒè¯è®¢å•明细的分配数量(防止并发操作)
            if (orderDetail.AllocatedQuantity < actualPickedQty)
            {
                throw new InvalidOperationException($"订单明细分配数量不足,需要拣选 {actualPickedQty},可用分配数量 {orderDetail.AllocatedQuantity}");
            }
            // æ¢å¤åŽŸé”å®šä¿¡æ¯
            var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
            if (orderDetail.LockQuantity < actualPickedQty)
            {
                throw new InvalidOperationException($"订单明细锁定数量不足,需要拣选 {actualPickedQty},可用锁定数量 {orderDetail.LockQuantity}");
            }
            originalLockInfo.AssignQuantity += splitRecord.SplitQty;
            originalLockInfo.OrderQuantity += splitRecord.SplitQty;
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
            // 1. æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += actualPickedQty;
            _logger.LogInformation($"更新锁定信息 - å·²æ‹£é€‰æ•°é‡ä»Ž {lockInfo.PickedQty - actualPickedQty} å¢žåŠ åˆ° {lockInfo.PickedQty}");
            // åˆ é™¤æ–°åº“存明细
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == newLockInfo.CurrentBarcode)
                .ExecuteCommandAsync();
            // å‡†ç¡®åˆ¤æ–­æ‹£é€‰å®ŒæˆçŠ¶æ€
            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($"锁定信息状态保持为出库中(部分拣选)");
            }
            // åˆ é™¤æ–°é”å®šä¿¡æ¯
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == newLockInfo.Id)
                .ExecuteCommandAsync();
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ ‡è®°æ‹†åŒ…记录为已撤销
            splitRecord.IsReverted = true;
            splitRecord.RevertTime = DateTime.Now;
            splitRecord.RevertOperator = App.User.UserName;
            await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
            // 2. æ›´æ–°åº“存信息
            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
        #region æ•°æ®æ›´æ–°æ–¹æ³•
        private async Task UpdateBatchAndOrderData(Dt_OutboundBatch batch, Dt_OutboundOrderDetail orderDetail, decimal pickedQty, string orderNo)
        {
            // æ›´æ–°æ‰¹æ¬¡å®Œæˆæ•°é‡
            _logger.LogInformation($"开始更新批次和订单数据 - æ‹£é€‰æ•°é‡: {pickedQty}");
            // é‡è¦ä¿®å¤ï¼šéªŒè¯åˆ†é…æ•°é‡ä¸ä¼šå˜æˆè´Ÿæ•°
            if (orderDetail.AllocatedQuantity < pickedQty)
            {
                throw new InvalidOperationException($"更新订单数据时分配数量不足,需要减少 {pickedQty},当前分配数量 {orderDetail.AllocatedQuantity}");
            }
            if (orderDetail.LockQuantity < pickedQty)
            {
                throw new InvalidOperationException($"更新订单数据时锁定数量不足,需要减少 {pickedQty},当前锁定数量 {orderDetail.LockQuantity}");
            }
            // 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();
            // æ›´æ–°è®¢å•明细
            // 2. æ›´æ–°è®¢å•明细
            decimal originalOverOutQty = orderDetail.OverOutQuantity;
            decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
            decimal originalLockQty = orderDetail.LockQuantity;
            orderDetail.OverOutQuantity += pickedQty;
            orderDetail.AllocatedQuantity -= pickedQty;
            // LockQuantity åŒæ­¥å‡å°‘
            orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
            _logger.LogInformation($"更新订单明细 - å·²å‡ºåº“数量从 {originalOverOutQty} å¢žåŠ åˆ° {orderDetail.OverOutQuantity}");
            _logger.LogInformation($"更新订单明细 - å·²åˆ†é…æ•°é‡ä»Ž {originalAllocatedQty} å‡å°‘到 {orderDetail.AllocatedQuantity}");
            _logger.LogInformation($"更新订单明细 - é”å®šæ•°é‡ä»Ž {originalLockQty} å‡å°‘到 {orderDetail.LockQuantity}");
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            // æ£€æŸ¥è®¢å•状态
            // 3. æ£€æŸ¥è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
            _logger.LogInformation($"批次和订单数据更新完成");
        }
        private async Task RevertBatchAndOrderData(Dt_PickingRecord pickingRecord, RevertPickingResult revertResult)
        {
            // æ¢å¤æ‰¹æ¬¡å®Œæˆæ•°é‡
            _logger.LogInformation($"开始恢复批次和订单数据");
            // 1. æ¢å¤æ‰¹æ¬¡å®Œæˆæ•°é‡
            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;
                _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();
            }
            // æ¢å¤è®¢å•明细
            // 2. æ¢å¤è®¢å•明细
            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;
                // æ³¨æ„ï¼šAllocatedQuantity å’Œ LockQuantity åœ¨å–消分拣时不应该改变
                _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,6 +1981,11 @@
                if (orderDetail != null)
                {
                    orderDetail.AllocatedQuantity -= returnedQty;
                    // LockQuantity åŒæ­¥å‡å°‘,保持与已分配数量一致
                    orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
                    await UpdateBatchAllocateStatus(orderDetail);
                    await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                }
            }
@@ -1340,7 +2000,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 +2012,11 @@
                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
            };
            await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
@@ -1414,9 +2078,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
        {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -417,6 +417,7 @@
                    // æ›´æ–°æ˜Žç»†çš„已分配数量
                    detail.AllocatedQuantity += assignQuantity;
                    detail.LockQuantity = detail.AllocatedQuantity;
                    remainingAllocate -= assignQuantity;
                    allocatedQuantity += assignQuantity;
                }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -186,7 +186,7 @@
        public List<Dt_StockInfo> GetStockInfos(string materielCode, string lotNo, string supplyCode, List<string> locationCodes)
        {
            var query = Db.Queryable<Dt_StockInfo>()
             .Where(x => locationCodes.Contains(x.LocationCode)
             .Where(x => locationCodes.Contains(x.LocationCode) && x.StockStatus==StockStatusEmun.入库完成.ObjToInt()
             //  && x.StockStatus == (int)StockStatusEmun.正常)
             ).Includes(x => x.Details);