pan
2025-11-29 6641d42d35d7b9739c64fe578d69e43a39e26c16
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundBatchPickingService.cs
@@ -21,7 +21,7 @@
namespace WIDESEA_OutboundService
{
    public  class OutboundBatchPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>
    public class OutboundBatchPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>, IOutboundBatchPickingService
    {
 
@@ -86,72 +86,327 @@
        /// <summary>
        /// åˆ†æ‰¹åˆ†æ‹£ç¡®è®¤
        /// </summary>
        public async Task<WebResponseContent> ConfirmBatchPicking(string orderNo, string batchNo, string palletCode, string barcode, decimal actualPickedQty)
        public async Task<WebResponseContent> ConfirmBatchPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯åˆ†æ‹£è¯·æ±‚
                var validationResult = await ValidateBatchPickingRequest(orderNo, batchNo, palletCode, barcode, actualPickedQty);
                var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (lockInfo, orderDetail, stockDetail) = validationResult.Data;
                var (lockInfo, orderDetail, stockDetail, batch) = validationResult.Data;
                // ä½¿ç”¨é”å®šä¿¡æ¯çš„分配数量作为实际分拣数量
                var actualPickedQty = lockInfo.AssignQuantity;
                // 2. æ‰§è¡Œåˆ†æ‹£é€»è¾‘
                var pickingResult = await ExecuteBatchPickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
                var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
                // 3. æ›´æ–°æ‰¹æ¬¡å®Œæˆæ•°é‡
                await UpdateBatchCompletedQuantity(batchNo, actualPickedQty);
                // 3. æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•数据
                await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo);
                // 4. æ›´æ–°è®¢å•相关数据
                await UpdateOrderRelatedData(orderDetail.Id, actualPickedQty, orderNo);
                // 5. è®°å½•拣选历史
                await RecordPickingHistory(pickingResult, orderNo, palletCode, batchNo);
                // 4. è®°å½•拣选历史
                await RecordPickingHistory(pickingResult, orderNo, palletCode);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("分批分拣成功");
                return WebResponseContent.Instance.OK("分拣成功", new
                {
                    PickedQuantity = actualPickedQty,
                    Barcode = barcode,
                    MaterialCode = lockInfo.MaterielCode
                });
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"分批分拣失败 - OrderNo: {orderNo}, BatchNo: {batchNo}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"分批分拣失败:{ex.Message}");
                _logger.LogError($"分拣失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"分拣失败:{ex.Message}");
            }
        }
        /// <summary>
        /// å–消分拣
        /// </summary>
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾åˆ†æ‹£è®°å½•
                var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.PalletCode == palletCode &&
                               x.Barcode == barcode &&
                               !x.IsCancelled)
                    .OrderByDescending(x => x.PickTime)
                    .FirstAsync();
                if (pickingRecord == null)
                    return WebResponseContent.Instance.Error("未找到分拣记录");
                // æ¢å¤é”å®šä¿¡æ¯å’Œåº“å­˜
                await RevertPickingData(pickingRecord);
                //更新批次和订单数据
                await RevertBatchAndOrderData(pickingRecord);
                // æ ‡è®°åˆ†æ‹£è®°å½•为已取消
                pickingRecord.IsCancelled = true;
                pickingRecord.CancelTime = DateTime.Now;
                pickingRecord.CancelOperator = App.User.UserName;
                await Db.Updateable(pickingRecord).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取消分拣成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"取消分拣失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>> ValidateBatchPickingRequest(
            string orderNo, string batchNo, string palletCode, string barcode, decimal actualPickedQty)
        #endregion
        #region æ‰‹åŠ¨æ‹†åŒ…
        /// <summary>
        /// æ‰‹åŠ¨æ‹†åŒ…
        /// </summary>
        public async Task<WebResponseContent> ManualSplitPackage(string orderNo, string palletCode, string originalBarcode, decimal splitQuantity)
        {
            // æŸ¥æ‰¾æ‰¹æ¬¡é”å®šä¿¡æ¯
            try
            {
                _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);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("手动拆包成功", new
                {
                    NewBarcode = splitResult.NewBarcode,
                    OriginalBarcode = originalBarcode,
                    SplitQuantity = splitQuantity
                });
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"手动拆包失败 - OrderNo: {orderNo}, Barcode: {originalBarcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"手动拆包失败:{ex.Message}");
            }
        }
        #endregion
        #region å–消拆包
        /// <summary>
        /// å–消拆包
        /// </summary>
        public async Task<WebResponseContent> CancelSplitPackage(string orderNo, string palletCode, string newBarcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾æ‹†åŒ…记录并验证
                var validationResult = await ValidateCancelSplitRequest(orderNo, palletCode, newBarcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (splitRecord, newLockInfo, newStockDetail) = validationResult.Data;
                // æ‰§è¡Œå–消拆包逻辑
                await ExecuteCancelSplitLogic(splitRecord, newLockInfo, newStockDetail);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取消拆包成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"取消拆包失败 - OrderNo: {orderNo}, Barcode: {newBarcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消拆包失败:{ex.Message}");
            }
        }
        #endregion
        #region åˆ†æ‰¹å›žåº“
        /// <summary>
        /// åˆ†æ‰¹å›žåº“ - é‡Šæ”¾æœªæ‹£é€‰çš„库存
        /// </summary>
        public async Task<WebResponseContent> BatchReturnStock(string orderNo, string palletCode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                //  æŸ¥æ‰¾æ‰˜ç›˜ä¸Šæœªå®Œæˆçš„锁定记录
                var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.PalletCode == palletCode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
                if (!unfinishedLocks.Any())
                    return WebResponseContent.Instance.Error("该托盘没有未完成的锁定记录");
                // æŒ‰æ‰¹æ¬¡åˆ†ç»„处理
                var batchGroups = unfinishedLocks.GroupBy(x => x.BatchNo);
                foreach (var batchGroup in batchGroups)
                {
                    var batchNo = batchGroup.Key;
                    var batchLocks = batchGroup.ToList();
                    // é‡Šæ”¾åº“存和锁定记录
                    foreach (var lockInfo in batchLocks)
                    {
                        await ReleaseLockAndStock(lockInfo);
                    }
                    // æ›´æ–°æ‰¹æ¬¡çŠ¶æ€
                    await UpdateBatchStatusForReturn(batchNo, batchLocks);
                    // æ›´æ–°è®¢å•明细的已分配数量
                    await UpdateOrderDetailAfterReturn(batchLocks);
                }
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("分批回库成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"分批回库失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"分批回库失败:{ex.Message}");
            }
        }
        #endregion
        #region éªŒè¯æ–¹æ³•
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>> ValidatePickingRequest(
    string orderNo, string palletCode, string barcode)
        {
            // æŸ¥æ‰¾é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.BatchNo == batchNo &&
                           x.PalletCode == palletCode &&
                           x.CurrentBarcode == barcode &&
                           x.Status == (int)OutLockStockStatusEnum.出库中)
                .FirstAsync();
            if (lockInfo == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("未找到有效的批次锁定信息");
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("未找到有效的锁定信息");
            if (actualPickedQty <= 0 || actualPickedQty > lockInfo.AssignQuantity - lockInfo.PickedQty)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("分拣数量无效");
            // æ£€æŸ¥æ˜¯å¦å·²ç»åˆ†æ‹£å®Œæˆ
            if (lockInfo.PickedQty >= lockInfo.AssignQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("该条码已分拣完成");
            // èŽ·å–è®¢å•æ˜Žç»†å’Œåº“å­˜æ˜Žç»†
            // èŽ·å–å…³è”æ•°æ®
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == barcode && x.StockId == lockInfo.StockId);
            return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Success((lockInfo, orderDetail, stockDetail));
            // éªŒè¯åº“存数量
            if (stockDetail.StockQuantity < lockInfo.AssignQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
                    $"库存数量不足,需要:{lockInfo.AssignQuantity},实际:{stockDetail.StockQuantity}");
            var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
                .FirstAsync(x => x.BatchNo == lockInfo.BatchNo);
            return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Success((lockInfo, orderDetail, stockDetail, batch));
        }
        private async Task<PickingResult> ExecuteBatchPickingLogic(
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateSplitRequest(
            string orderNo, string palletCode, string originalBarcode, decimal 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)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到有效的锁定信息");
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == originalBarcode && x.StockId == lockInfo.StockId);
            if (stockDetail.StockQuantity < splitQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("拆包数量不能大于库存数量");
            if (lockInfo.AssignQuantity - lockInfo.PickedQty < splitQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("拆包数量不能大于未拣选数量");
            return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((lockInfo, stockDetail));
        }
        private async Task<ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateCancelSplitRequest(
            string orderNo, string palletCode, string newBarcode)
        {
            var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(x => x.NewBarcode == newBarcode &&
                           x.OrderNo == orderNo &&
                           !x.IsReverted)
                .FirstAsync();
            if (splitRecord == null)
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到拆包记录");
            var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.CurrentBarcode == newBarcode &&
                           x.PalletCode == palletCode &&
                           x.OrderNo == orderNo)
                .FirstAsync();
            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();
            if (pickingRecord != null)
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("该条码已被分拣,无法取消拆包");
            var newStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == newBarcode);
            return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((splitRecord, newLockInfo, newStockDetail));
        }
        #endregion
        #region æ ¸å¿ƒé€»è¾‘方法
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
            Dt_StockInfoDetail stockDetail, decimal actualPickedQty)
        {
@@ -180,86 +435,34 @@
            };
        }
        private async Task UpdateBatchCompletedQuantity(string batchNo, decimal pickedQty)
        private async Task<RevertPickingResult> RevertPickingData(Dt_PickingRecord pickingRecord)
        {
            await _outboundBatchRepository.Db.Updateable<Dt_OutboundBatch>()
                .SetColumns(x => x.CompletedQuantity == x.CompletedQuantity + pickedQty)
                .Where(x => x.BatchNo == batchNo)
                .ExecuteCommandAsync();
            // æ£€æŸ¥æ‰¹æ¬¡æ˜¯å¦å®Œæˆ
            var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
                .FirstAsync(x => x.BatchNo == batchNo);
            if (batch.CompletedQuantity >= batch.BatchQuantity)
            {
                batch.BatchStatus = (int)BatchStatusEnum.已完成;
                await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
            }
        }
        #endregion
        #region æ‰‹åŠ¨æ‹†åŒ…
        /// <summary>
        /// æ‰‹åŠ¨æ‹†åŒ…
        /// </summary>
        public async Task<WebResponseContent> ManualSplitPackage(string orderNo, string batchNo, string originalBarcode, decimal splitQuantity)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯æ‹†åŒ…请求
                var validationResult = await ValidateManualSplitRequest(orderNo, batchNo, originalBarcode, splitQuantity);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (lockInfo, stockDetail) = validationResult.Data;
                // 2. æ‰§è¡Œæ‹†åŒ…逻辑
                var splitResult = await ExecuteManualSplit(lockInfo, stockDetail, splitQuantity, batchNo);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("手动拆包成功", new { NewBarcode = splitResult.NewBarcode });
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"手动拆包失败 - OrderNo: {orderNo}, BatchNo: {batchNo}, Barcode: {originalBarcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"手动拆包失败:{ex.Message}");
            }
        }
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateManualSplitRequest(
            string orderNo, string batchNo, string originalBarcode, decimal splitQuantity)
        {
            // æ¢å¤é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.BatchNo == batchNo &&
                           x.CurrentBarcode == originalBarcode &&
                           x.Status == (int)OutLockStockStatusEnum.出库中)
                .FirstAsync();
                .FirstAsync(x => x.Id == pickingRecord.OutStockLockId);
            if (lockInfo == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到有效的锁定信息");
            lockInfo.PickedQty -= pickingRecord.PickQuantity;
            lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // æ¢å¤åº“å­˜
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == originalBarcode && x.StockId == lockInfo.StockId);
                .FirstAsync(x => x.Barcode == pickingRecord.Barcode);
            if (stockDetail.StockQuantity < splitQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("拆包数量不能大于库存数量");
            stockDetail.StockQuantity += pickingRecord.PickQuantity;
            stockDetail.OutboundQuantity -= pickingRecord.PickQuantity;
            stockDetail.Status = (int)StockStatusEmun.出库锁定;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            if (lockInfo.AssignQuantity - lockInfo.PickedQty < splitQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("拆包数量不能大于未拣选数量");
            return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((lockInfo, stockDetail));
            return new RevertPickingResult
            {
                LockInfo = lockInfo,
                StockDetail = stockDetail
            };
        }
        private async Task<SplitResultDto> ExecuteManualSplit(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal splitQuantity, string batchNo)
        private async Task<SplitResultDto> ExecuteSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal splitQuantity, string palletCode)
        {
            // ç”Ÿæˆæ–°æ¡ç 
            string newBarcode = await GenerateNewBarcode();
@@ -289,14 +492,14 @@
            {
                OrderNo = lockInfo.OrderNo,
                OrderDetailId = lockInfo.OrderDetailId,
                BatchNo = batchNo,
                OutboundBatchNo = lockInfo.OutboundBatchNo,
                MaterielCode = lockInfo.MaterielCode,
                StockId = lockInfo.StockId,
                OrderQuantity = splitQuantity,
                AssignQuantity = splitQuantity,
                PickedQty = 0,
                LocationCode = lockInfo.LocationCode,
                PalletCode = lockInfo.PalletCode,
                PalletCode = palletCode,
                Status = (int)OutLockStockStatusEnum.出库中,
                CurrentBarcode = newBarcode,
                Operator = App.User.UserName, 
@@ -305,6 +508,7 @@
            // æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity -= splitQuantity;
            lockInfo.OrderQuantity -= splitQuantity;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // è®°å½•拆包历史
@@ -313,34 +517,8 @@
            return new SplitResultDto { NewBarcode = newBarcode };
        }
        #endregion
        #region å–消拆包
        /// <summary>
        /// å–消拆包
        /// </summary>
        public async Task<WebResponseContent> CancelSplitPackage(string orderNo, string batchNo, string newBarcode)
        private async Task ExecuteCancelSplitLogic(Dt_SplitPackageRecord splitRecord, Dt_OutStockLockInfo newLockInfo, Dt_StockInfoDetail newStockDetail)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾æ‹†åŒ…记录和新锁定信息
                var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.NewBarcode == newBarcode && x.OrderNo == orderNo && !x.IsReverted)
                    .FirstAsync();
                if (splitRecord == null)
                    return WebResponseContent.Instance.Error("未找到拆包记录");
                var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == newBarcode && x.BatchNo == batchNo)
                    .FirstAsync();
                if (newLockInfo == null)
                    return WebResponseContent.Instance.Error("未找到新锁定信息");
                // æ¢å¤åŽŸåº“å­˜
                var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
@@ -353,11 +531,12 @@
                    .FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
                originalLockInfo.AssignQuantity += splitRecord.SplitQty;
            originalLockInfo.OrderQuantity += splitRecord.SplitQty;
                await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
                // åˆ é™¤æ–°åº“存明细
                await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == newBarcode)
                .Where(x => x.Barcode == newLockInfo.CurrentBarcode)
                    .ExecuteCommandAsync();
                // åˆ é™¤æ–°é”å®šä¿¡æ¯
@@ -368,66 +547,53 @@
                // æ ‡è®°æ‹†åŒ…记录为已撤销
                splitRecord.IsReverted = true;
                splitRecord.RevertTime = DateTime.Now;
                splitRecord.Operator = App.User.UserName;
            splitRecord.RevertOperator = App.User.UserName;
                await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取消拆包成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"取消拆包失败 - OrderNo: {orderNo}, BatchNo: {batchNo}, Barcode: {newBarcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消拆包失败:{ex.Message}");
            }
        }
        #endregion
        #region åˆ†æ‰¹å›žåº“
        #region æ•°æ®æ›´æ–°æ–¹æ³•
        /// <summary>
        /// åˆ†æ‰¹å›žåº“ - é‡Šæ”¾æœªæ‹£é€‰çš„库存
        /// </summary>
        public async Task<WebResponseContent> BatchReturnStock(string orderNo, string batchNo)
        private async Task UpdateBatchAndOrderData(Dt_OutboundBatch batch, Dt_OutboundOrderDetail orderDetail, decimal pickedQty, string orderNo)
        {
            try
            // æ›´æ–°æ‰¹æ¬¡å®Œæˆæ•°é‡
            batch.CompletedQuantity += pickedQty;
            if (batch.CompletedQuantity >= batch.BatchQuantity)
            {
                _unitOfWorkManage.BeginTran();
                batch.BatchStatus = (int)BatchStatusEnum.已完成;
            }
            await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
                // 1. æŸ¥æ‰¾æ‰¹æ¬¡æœªå®Œæˆçš„锁定记录
                var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.BatchNo == batchNo &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
            // æ›´æ–°è®¢å•明细
            orderDetail.OverOutQuantity += pickedQty;
            orderDetail.AllocatedQuantity -= pickedQty;
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                if (!unfinishedLocks.Any())
                    return WebResponseContent.Instance.Error("该批次没有未完成的锁定记录");
                // 2. é‡Šæ”¾åº“存和锁定记录
                foreach (var lockInfo in unfinishedLocks)
                {
                    await ReleaseLockAndStock(lockInfo);
            // æ£€æŸ¥è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
                }
                // 3. æ›´æ–°æ‰¹æ¬¡çŠ¶æ€
                await UpdateBatchStatusForReturn(batchNo);
                // 4. æ›´æ–°è®¢å•明细的已分配数量
                await UpdateOrderDetailAfterReturn(unfinishedLocks);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("分批回库成功");
            }
            catch (Exception ex)
        private async Task RevertBatchAndOrderData(Dt_PickingRecord pickingRecord)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"分批回库失败 - OrderNo: {orderNo}, BatchNo: {batchNo}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"分批回库失败:{ex.Message}");
            }
            // æ¢å¤æ‰¹æ¬¡å®Œæˆæ•°é‡
            var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
                .FirstAsync(x => x.BatchNo == pickingRecord.BatchNo);
            batch.CompletedQuantity -= pickingRecord.PickQuantity;
            batch.BatchStatus = (int)BatchStatusEnum.执行中;
            await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
            // æ¢å¤è®¢å•明细
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
            orderDetail.OverOutQuantity -= pickingRecord.PickQuantity;
            orderDetail.AllocatedQuantity += pickingRecord.PickQuantity;
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            // é‡æ–°æ£€æŸ¥è®¢å•状态
            await CheckAndUpdateOrderStatus(pickingRecord.OrderNo);
        }
        private async Task ReleaseLockAndStock(Dt_OutStockLockInfo lockInfo)
@@ -443,20 +609,30 @@
            }
            // æ›´æ–°é”å®šè®°å½•状态为回库
            lockInfo.Status = (int)OutLockStockStatusEnum.回库中;
            lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
        }
        private async Task UpdateBatchStatusForReturn(string batchNo)
        private async Task UpdateBatchStatusForReturn(string batchNo, List<Dt_OutStockLockInfo> returnedLocks)
        {
            await _outboundBatchRepository.Db.Updateable<Dt_OutboundBatch>()
                .SetColumns(x => new Dt_OutboundBatch
            var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
                .FirstAsync(x => x.BatchNo == batchNo);
            // è®¡ç®—回库数量
            var returnedQty = returnedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
            batch.CompletedQuantity -= returnedQty;
            if (batch.CompletedQuantity <= 0)
                {
                    BatchStatus = (int)BatchStatusEnum.已回库,
                    Operator = App.User.UserName
                })
                .Where(x => x.BatchNo == batchNo)
                .ExecuteCommandAsync();
                batch.BatchStatus = (int)BatchStatusEnum.已回库;
            }
            else
            {
                batch.BatchStatus = (int)BatchStatusEnum.执行中;
            }
            batch.Operator = App.User.UserName;
            await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
        }
        private async Task UpdateOrderDetailAfterReturn(List<Dt_OutStockLockInfo> returnedLocks)
@@ -482,7 +658,7 @@
        private async Task<string> GenerateNewBarcode()
        {
            var seq = await _dailySequenceService.GetNextSequenceAsync();
            return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
            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)
@@ -503,12 +679,12 @@
            await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
        }
        private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode, string batchNo)
        private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode)
        {
            var pickingRecord = new Dt_PickingRecord
            {
                OrderNo = orderNo,
               // BatchNo = batchNo,
                // BatchNo = result.FinalLockInfo.BatchNo,
                OrderDetailId = result.FinalLockInfo.OrderDetailId,
                PalletCode = palletCode,
                Barcode = result.FinalLockInfo.CurrentBarcode,
@@ -516,47 +692,60 @@
                PickQuantity = result.ActualPickedQty,
                PickTime = DateTime.Now,
                Operator = App.User.UserName,
                OutStockLockId = result.FinalLockInfo.Id
                OutStockLockId = result.FinalLockInfo.Id,
                //  IsCancelled = false
            };
            await Db.Insertable(pickingRecord).ExecuteCommandAsync();
        }
        private async Task UpdateOrderRelatedData(int orderDetailId, decimal pickedQty, string orderNo)
        {
            // æ›´æ–°è®¢å•明细的已出库数量
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(x => new Dt_OutboundOrderDetail
                {
                    OverOutQuantity = x.OverOutQuantity + pickedQty,
                    AllocatedQuantity = x.AllocatedQuantity - pickedQty
                })
                .Where(x => x.Id == orderDetailId)
                .ExecuteCommandAsync();
            // æ£€æŸ¥è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
        }
        private async Task CheckAndUpdateOrderStatus(string orderNo)
        {
            var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                  .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
                  .Where((o, item) => item.OrderNo == orderNo)
                  .Select((o, item) => o)
                .LeftJoin<Dt_OutboundOrder>((detail, order) => detail.OrderId == order.Id)
                .Where((detail, order) => order.OrderNo == orderNo)
                .Select((detail, order) => detail)
                  .ToListAsync();
            bool allCompleted = orderDetails.All(x => x.OverOutQuantity >= x.NeedOutQuantity);
            if (allCompleted)
            {
            var orderStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => new Dt_OutboundOrder { OrderStatus = (int)OutOrderStatusEnum.出库完成 })
                .SetColumns(x => x.OrderStatus == orderStatus)
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
        #endregion
        #region DTOç±»
        public class PickingResult
        {
            public Dt_OutStockLockInfo FinalLockInfo { get; set; }
            public decimal ActualPickedQty { get; set; }
        }
        public class RevertPickingResult
        {
            public Dt_OutStockLockInfo LockInfo { get; set; }
            public Dt_StockInfoDetail StockDetail { get; set; }
        }
        public class SplitResultDto
        {
            public string NewBarcode { get; set; }
        }
        public class ValidationResult<T>
        {
            public bool IsValid { get; set; }
            public string ErrorMessage { get; set; }
            public T Data { get; set; }
            public static ValidationResult<T> Success(T data) => new ValidationResult<T> { IsValid = true, Data = data };
            public static ValidationResult<T> Error(string message) => new ValidationResult<T> { IsValid = false, ErrorMessage = message };
        }
        #endregion