pan
2025-11-29 fcdbb4d6cc8eb3629b871a4945ff2da599d64107
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundBatchPickingService.cs
@@ -6,11 +6,13 @@
using System.Text;
using System.Threading.Tasks;
using WIDESEA_BasicService;
using WIDESEA_Common.CommonEnum;
using WIDESEA_Common.OrderEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Outbound;
using WIDESEA_IAllocateService;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
@@ -18,13 +20,14 @@
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Basic;
using WIDESEA_Model.Models.Outbound;
using static WIDESEA_OutboundService.OutboundBatchPickingService;
namespace WIDESEA_OutboundService
{
    public  class OutboundBatchPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>
    public class OutboundBatchPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>, IOutboundBatchPickingService
    {
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        public IRepository<Dt_PickingRecord> Repository => BaseDal;
@@ -61,7 +64,7 @@
        public OutboundBatchPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService,
            IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService,
            IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService,
            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService) : base(BaseDal)
            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_OutboundBatch> outboundBatchRepository) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -78,80 +81,991 @@
            _invokeMESService = invokeMESService;
            _dailySequenceService = dailySequenceService;
            _allocateService = allocateService;
            _outboundBatchRepository = outboundBatchRepository;
        }
        // <summary>
        /// èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
        /// </summary>
        public async Task<List<PalletLockInfoDto>> GetPalletLockInfos(string orderNo, string palletCode)
        {
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                  .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                  .Select(x => new
                  {
                      x.Id,
                      x.OrderNo,
                      x.BatchNo,
                      x.MaterielCode,
                      x.CurrentBarcode,
                      x.AssignQuantity,
                      x.PickedQty,
                      x.Status,
                      x.LocationCode,
                      x.PalletCode
                  }).ToListAsync();
            var lockInfoDtos = lockInfos.Select(x => new PalletLockInfoDto
            {
                Id = x.Id,
                OrderNo = x.OrderNo,
                BatchNo = x.BatchNo,
                MaterielCode = x.MaterielCode,
                CurrentBarcode = x.CurrentBarcode,
                AssignQuantity = x.AssignQuantity,
                PickedQty = x.PickedQty,
                Status = x.Status,
                LocationCode = x.LocationCode,
                PalletCode = x.PalletCode,
                CanSplit = (x.Status == (int)OutLockStockStatusEnum.出库中 && x.AssignQuantity - x.PickedQty > 0),
                CanPick = (x.Status == (int)OutLockStockStatusEnum.出库中 && x.PickedQty < x.AssignQuantity)
            }).ToList();
            return lockInfoDtos;
        }
        #region æŸ¥è¯¢æ–¹æ³•
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å·²æ‹£é€‰åˆ—è¡¨
        /// </summary>
        public async Task<List<PalletPickedInfoDto>> GetPalletPickedList(string orderNo, string palletCode)
        {
            var pickedList = await Db.Queryable<Dt_PickingRecord>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           !x.IsCancelled)
                .Select(x => new PalletPickedInfoDto
                {
                    Id = x.Id,
                    OrderNo = x.OrderNo,
                    OrderDetailId = x.OrderDetailId,
                    PalletCode = x.PalletCode,
                    Barcode = x.Barcode,
                    MaterielCode = x.MaterielCode,
                    PickedQty = x.PickQuantity,
                    PickTime = x.PickTime,
                    Operator = x.Operator,
                    LocationCode = x.LocationCode
                })
                .ToListAsync();
            return pickedList;
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çŠ¶æ€
        /// </summary>
        public async Task<PalletStatusDto> GetPalletStatus(string orderNo, string palletCode)
        {
            // èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .ToListAsync();
            if (!lockInfos.Any())
            {
                return new PalletStatusDto
                {
                    OrderNo = orderNo,
                    PalletCode = palletCode,
                    Status = (int)PalletStatusEnum.无任务,
                    StatusText = "无任务",
                    TotalItems = 0,
                    CompletedItems = 0,
                    PendingItems = 0
                };
            }
            var totalItems = lockInfos.Count;
            var completedItems = lockInfos.Count(x => x.Status == (int)OutLockStockStatusEnum.拣选完成);
            var pendingItems = lockInfos.Count(x => x.Status == (int)OutLockStockStatusEnum.出库中);
            var status = PalletStatusEnum.拣选中;
            if (pendingItems == 0 && completedItems > 0)
            {
                status = PalletStatusEnum.已完成;
            }
            else if (pendingItems > 0 && completedItems == 0)
            {
                status = PalletStatusEnum.未开始;
            }
            else if (pendingItems > 0 && completedItems > 0)
            {
                status = PalletStatusEnum.拣选中;
            }
            return new PalletStatusDto
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                Status = (int)status,
                StatusText = GetPalletStatusText(status),
                TotalItems = totalItems,
                CompletedItems = completedItems,
                PendingItems = pendingItems
            };
        }
        /// <summary>
        /// èŽ·å–æ‹†åŒ…ä¿¡æ¯
        /// </summary>
        public async Task<SplitPackageInfoDto> GetSplitPackageInfo(string orderNo, string palletCode, string barcode)
        {
            // æŸ¥æ‰¾é”å®šä¿¡æ¯
            var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.CurrentBarcode == barcode
                        //&& x.Status == (int)OutLockStockStatusEnum.出库中
                          )
                .FirstAsync();
            if (lockInfo == null)
                throw new Exception("未找到有效的锁定信息");
            // è®¡ç®—剩余可拆数量
            var remainQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
            return new SplitPackageInfoDto
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                Barcode = barcode,
                MaterielCode = lockInfo.MaterielCode,
                RemainQuantity = remainQuantity,
                AssignQuantity = lockInfo.AssignQuantity,
                PickedQty = lockInfo.PickedQty
            };
        }
        #endregion
        #region å–走空箱逻辑
        /// <summary>
        /// å–走空箱 - æ¸…理已完成拣选的托盘数据
        /// </summary>
        public async Task<WebResponseContent> RemoveEmptyPallet(string orderNo, string palletCode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                //  éªŒè¯æ‰˜ç›˜æ˜¯å¦å¯ä»¥å–走(必须全部完成拣选)
                var validationResult = await ValidateEmptyPalletRemoval(orderNo, palletCode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var completedLocks = validationResult.Data;
                // æ¸…理锁定记录(标记为已完成)
                await CleanupCompletedLocks(completedLocks);
                // æ›´æ–°ç›¸å…³è®¢å•状态
                await UpdateOrderStatusAfterPalletRemoval(orderNo);
                //  è®°å½•操作历史
                // await RecordEmptyPalletRemoval(orderNo, palletCode, completedLocks);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取走空箱成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"取走空箱失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取走空箱失败:{ex.Message}");
            }
        }
        /// <summary>
        /// éªŒè¯ç©ºç®±å–走条件
        /// </summary>
        private async Task<ValidationResult<List<Dt_OutStockLockInfo>>> ValidateEmptyPalletRemoval(string orderNo, string palletCode)
        {
            // èŽ·å–æ‰˜ç›˜çš„æ‰€æœ‰é”å®šè®°å½•
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .ToListAsync();
            if (!lockInfos.Any())
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有锁定记录");
            // æ£€æŸ¥æ˜¯å¦æœ‰æœªå®Œæˆçš„锁定记录
            var unfinishedLocks = lockInfos.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.出库中 ||
                x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
            if (unfinishedLocks.Any())
            {
                var unfinishedCount = unfinishedLocks.Count;
                var unfinishedQty = unfinishedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
                    $"托盘还有{unfinishedCount}条未完成记录,剩余数量{unfinishedQty},不能取走空箱");
            }
            // èŽ·å–å·²å®Œæˆçš„é”å®šè®°å½•
            var completedLocks = lockInfos.Where(x =>
                x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
            if (!completedLocks.Any())
                return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有已完成拣选的记录");
            return ValidationResult<List<Dt_OutStockLockInfo>>.Success(completedLocks);
        }
        /// <summary>
        /// æ¸…理已完成的锁定记录
        /// </summary>
        private async Task CleanupCompletedLocks(List<Dt_OutStockLockInfo> completedLocks)
        {
            foreach (var lockInfo in completedLocks)
            {
                // æ ‡è®°é”å®šè®°å½•为已取走(可以新增状态或直接删除,根据业务需求)
                // è¿™é‡Œæˆ‘们将其状态更新为"已取走",并记录取走时间
                lockInfo.Status = (int)OutLockStockStatusEnum.已取走;
                lockInfo.Operator = App.User.UserName;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                // åŒæ—¶æ¸…理对应的库存记录状态
                await CleanupStockInfo(lockInfo);
            }
        }
        /// <summary>
        /// æ¸…理库存信息
        /// </summary>
        private async Task CleanupStockInfo(Dt_OutStockLockInfo lockInfo)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
            if (stockDetail != null)
            {
                // å¦‚果库存已经出库完成,标记为已清理
                if (stockDetail.Status == (int)StockStatusEmun.出库完成)
                {
                    stockDetail.Status = (int)StockStatusEmun.已清理;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                }
            }
        }
        /// <summary>
        /// æ›´æ–°è®¢å•状态
        /// </summary>
        private async Task UpdateOrderStatusAfterPalletRemoval(string orderNo)
        {
            // æ£€æŸ¥è®¢å•是否所有托盘都已完成
            var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo)
                .ToListAsync();
            var unfinishedPallets = allLocks
                .GroupBy(x => x.PalletCode)
                .Where(g => g.Any(x => x.Status == (int)OutLockStockStatusEnum.出库中 ||
                                      x.Status == (int)OutLockStockStatusEnum.回库中))
                .ToList();
            // å¦‚果没有未完成的托盘,更新订单状态为出库完成
            if (!unfinishedPallets.Any())
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => new Dt_OutboundOrder
                    {
                        OrderStatus = (int)OutOrderStatusEnum.出库完成,
                    })
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// è®°å½•空箱取走历史
        /// </summary>
        private async Task RecordEmptyPalletRemoval(string orderNo, string palletCode, List<Dt_OutStockLockInfo> completedLocks)
        {
            var removalRecord = new Dt_EmptyPalletRemoval
            {
                OrderNo = orderNo,
                PalletCode = palletCode,
                RemovalTime = DateTime.Now,
                Operator = App.User.UserName,
                CompletedItemsCount = completedLocks.Count,
                TotalPickedQuantity = completedLocks.Sum(x => x.PickedQty)
            };
            await Db.Insertable(removalRecord).ExecuteCommandAsync();
        }
        #endregion
        #region è¾…助方法
        private string GetPalletStatusText(PalletStatusEnum status)
        {
            return status switch
            {
                PalletStatusEnum.未开始 => "未开始",
                PalletStatusEnum.拣选中 => "拣选中",
                PalletStatusEnum.已完成 => "已完成",
                PalletStatusEnum.无任务 => "无任务",
                _ => "未知"
            };
        }
        #endregion
        #region åˆ†æ‰¹åˆ†æ‹£
        /// <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 å–消拆包
        #region å–消拆包 - ä¿®å¤ç‰ˆæœ¬
        /// <summary>
        /// å–消拆包 - æ”¯æŒå¤šæ¬¡æ‹†åŒ…的情况
        /// </summary>
        public async Task<WebResponseContent> CancelSplitPackage(string orderNo, string palletCode, string newBarcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. æŸ¥æ‰¾æ‹†åŒ…记录并验证
                var validationResult = await ValidateCancelSplitRequest(orderNo, palletCode, newBarcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (splitRecord, newLockInfo, newStockDetail) = validationResult.Data;
                // 2. æŸ¥æ‰¾åŽŸå§‹é”å®šä¿¡æ¯
                var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
                // 3. æ£€æŸ¥è¯¥æ¡ç æ˜¯å¦è¢«å†æ¬¡æ‹†åŒ…
                var childSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.OriginalBarcode == newBarcode && !x.IsReverted)
                    .ToListAsync();
                if (childSplitRecords.Any())
                {
                    return WebResponseContent.Instance.Error("该条码已被再次拆包,请先取消后续的拆包操作");
                }
                // 4. æ‰§è¡Œå–消拆包逻辑
                await ExecuteCancelSplitLogic(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取消拆包成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"取消拆包失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {newBarcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消拆包失败:{ex.Message}");
            }
        }
        /// <summary>
        /// æ‰§è¡Œå–消拆包逻辑 - ä¿®å¤ç‰ˆæœ¬
        /// </summary>
        private async Task ExecuteCancelSplitLogic(Dt_SplitPackageRecord splitRecord,
            Dt_OutStockLockInfo originalLockInfo, Dt_OutStockLockInfo newLockInfo,
            Dt_StockInfoDetail newStockDetail)
        {
            // 1. æ¢å¤åŽŸé”å®šä¿¡æ¯
            // æ³¨æ„ï¼šè¿™é‡Œéœ€è¦ç´¯åŠ ï¼Œè€Œä¸æ˜¯ç®€å•çš„èµ‹å€¼ï¼Œå› ä¸ºå¯èƒ½æœ‰å¤šæ¬¡æ‹†åŒ…
            originalLockInfo.AssignQuantity += splitRecord.SplitQty;
            originalLockInfo.OrderQuantity += splitRecord.SplitQty;
            // å¦‚果原锁定信息的状态是拣选完成,需要重新设置为出库中
            if (originalLockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
            {
                originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            }
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
            // 2. æ¢å¤åŽŸåº“å­˜æ˜Žç»†
            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.出库完成)
            {
                originalStock.Status = (int)StockStatusEmun.出库锁定;
            }
            await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
            // 3. åˆ é™¤æ–°é”å®šä¿¡æ¯
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == newLockInfo.Id)
                .ExecuteCommandAsync();
            // 4. åˆ é™¤æ–°åº“存明细
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == newLockInfo.CurrentBarcode)
                .ExecuteCommandAsync();
            // 5. æ ‡è®°æ‹†åŒ…记录为已撤销
            splitRecord.IsReverted = true;
            splitRecord.RevertTime = DateTime.Now;
            splitRecord.RevertOperator = App.User.UserName;
            await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
            // 6. æ£€æŸ¥å¹¶æ›´æ–°æ‰¹æ¬¡å’Œè®¢å•状态
            await CheckAndUpdateBatchStatus(originalLockInfo.BatchNo);
            await CheckAndUpdateOrderStatus(originalLockInfo.OrderNo);
        }
        /// <summary>
        /// éªŒè¯å–消拆包请求 - å¢žå¼ºç‰ˆæœ¬
        /// </summary>
        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 childSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(x => x.OriginalBarcode == newBarcode && !x.IsReverted)
                .ToListAsync();
            if (childSplitRecords.Any())
            {
                var childBarcodes = string.Join(", ", childSplitRecords.Select(x => x.NewBarcode));
                return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error(
                    $"该条码已被再次拆包,生成的新条码:{childBarcodes},请先取消后续拆包");
            }
            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 æ‰¹é‡å–消拆包链
        /// <summary>
        /// æ‰¹é‡å–消拆包链 - å–消某个条码及其所有后续拆包
        /// </summary>
        public async Task<WebResponseContent> CancelSplitPackageChain(string orderNo, string palletCode, string startBarcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. æŸ¥æ‰¾æ‰€æœ‰ç›¸å…³çš„æ‹†åŒ…记录(形成拆包链)
                var splitChain = await GetSplitPackageChain(orderNo, startBarcode);
                if (!splitChain.Any())
                    return WebResponseContent.Instance.Error("未找到拆包记录");
                // 2. æŒ‰æ‹†åŒ…顺序倒序取消(从最新的开始取消)
                var reversedChain = splitChain.OrderByDescending(x => x.SplitTime).ToList();
                foreach (var splitRecord in reversedChain)
                {
                    await CancelSingleSplitPackage(splitRecord, palletCode);
                }
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK($"成功取消拆包链,共{reversedChain.Count}次拆包操作");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"取消拆包链失败 - OrderNo: {orderNo}, StartBarcode: {startBarcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"取消拆包链失败:{ex.Message}");
            }
        }
        /// <summary>
        /// èŽ·å–æ‹†åŒ…é“¾ - æŸ¥æ‰¾æŸä¸ªæ¡ç çš„æ‰€æœ‰æ‹†åŒ…记录(包括后续拆包)
        /// </summary>
        public async Task<List<Dt_SplitPackageRecord>> GetSplitPackageChain(string orderNo, string startBarcode)
        {
            var allSplitRecords = new List<Dt_SplitPackageRecord>();
            var visitedBarcodes = new HashSet<string>(); // é˜²æ­¢å¾ªçŽ¯å¼•ç”¨
            // ä½¿ç”¨é˜Ÿåˆ—进行广度优先搜索
            var queue = new Queue<string>();
            queue.Enqueue(startBarcode);
            visitedBarcodes.Add(startBarcode);
            while (queue.Count > 0)
            {
                var currentBarcode = queue.Dequeue();
                // æŸ¥æ‰¾ä»¥å½“前条码为原条码的所有拆包记录
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.OriginalBarcode == currentBarcode &&
                               x.OrderNo == orderNo &&
                               !x.IsReverted)
                    .ToListAsync();
                foreach (var record in splitRecords)
                {
                    // é¿å…é‡å¤å¤„理
                    if (!visitedBarcodes.Contains(record.NewBarcode))
                    {
                        allSplitRecords.Add(record);
                        queue.Enqueue(record.NewBarcode);
                        visitedBarcodes.Add(record.NewBarcode);
                    }
                }
            }
            return allSplitRecords;
        }
        /// <summary>
        /// å–消单个拆包记录
        /// </summary>
        private async Task CancelSingleSplitPackage(Dt_SplitPackageRecord splitRecord, string palletCode)
        {
            // æŸ¥æ‰¾ç›¸å…³æ•°æ®
            var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.CurrentBarcode == splitRecord.NewBarcode &&
                           x.PalletCode == palletCode)
                .FirstAsync();
            var newStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == splitRecord.NewBarcode);
            var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
            // æ‰§è¡Œå–消逻辑
            await ExecuteCancelSplitLogic(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
        }
        #endregion
        #region æ‹†åŒ…信息查询增强
        /// <summary>
        /// èŽ·å–æ‹†åŒ…é“¾ä¿¡æ¯
        /// </summary>
        public async Task<WebResponseContent> GetSplitPackageChainInfo(string orderNo, string barcode)
        {
            try
            {
                var splitChain = await GetSplitPackageChain(orderNo, barcode);
                var chainInfo = new SplitPackageChainInfoDto
                {
                    OriginalBarcode = barcode,
                    TotalSplitTimes = splitChain.Count,
                    SplitChain = splitChain.Select(x => new SplitChainItemDto
                    {
                        SplitTime = x.SplitTime,
                        OriginalBarcode = x.OriginalBarcode,
                        NewBarcode = x.NewBarcode,
                        SplitQuantity = x.SplitQty,
                        Operator = x.Operator,
                        IsReverted = x.IsReverted
                    }).ToList()
                };
                return WebResponseContent.Instance.OK("获取成功", chainInfo);
            }
            catch (Exception ex)
            {
                _logger.LogError($"获取拆包链信息失败 - OrderNo: {orderNo}, Barcode: {barcode}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error("获取拆包链信息失败");
            }
        }
        /// <summary>
        /// æŸ¥æ‰¾æ ¹æ¡ç 
        /// </summary>
        public async Task<string> FindRootBarcode(string orderNo, string startBarcode)
        {
            var currentBarcode = startBarcode;
            var visited = new HashSet<string>();
            while (!string.IsNullOrEmpty(currentBarcode) && !visited.Contains(currentBarcode))
            {
                visited.Add(currentBarcode);
                // æŸ¥æ‰¾å½“前条码是否是由其他条码拆包而来
                var parentRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.NewBarcode == currentBarcode &&
                               x.OrderNo == orderNo &&
                               !x.IsReverted)
                    .FirstAsync();
                if (parentRecord == null)
                {
                    // æ²¡æœ‰çˆ¶çº§æ‹†åŒ…记录,说明这是根条码
                    return currentBarcode;
                }
                currentBarcode = parentRecord.OriginalBarcode;
            }
            // å¦‚果出现循环引用,返回起始条码
            return startBarcode;
        }
        #endregion
        #region æ›´æ–°æ‰¹æ¬¡çŠ¶æ€æ£€æŸ¥
        /// <summary>
        /// æ£€æŸ¥å¹¶æ›´æ–°æ‰¹æ¬¡çŠ¶æ€
        /// </summary>
        private async Task CheckAndUpdateBatchStatus(string batchNo)
        {
            var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
                .FirstAsync(x => x.BatchNo == batchNo);
            if (batch != null)
            {
                // é‡æ–°è®¡ç®—批次完成数量
                var batchLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.BatchNo == batchNo)
                    .ToListAsync();
                var completedQuantity = batchLocks.Where(x => x.Status == (int)OutLockStockStatusEnum.拣选完成)
                                                 .Sum(x => x.PickedQty);
                batch.CompletedQuantity = completedQuantity;
                // æ›´æ–°æ‰¹æ¬¡çŠ¶æ€
                if (batch.CompletedQuantity >= batch.BatchQuantity)
                {
                    batch.BatchStatus = (int)BatchStatusEnum.已完成;
                }
                else if (batch.CompletedQuantity > 0)
                {
                    batch.BatchStatus = (int)BatchStatusEnum.执行中;
                }
                else
                {
                    batch.BatchStatus = (int)BatchStatusEnum.分配中;
                }
                await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
            }
        }
        #endregion
        #region DTOç±»
        public class SplitPackageChainInfoDto
        {
            public string OriginalBarcode { get; set; }
            public string RootBarcode { get; set; } // æ–°å¢žï¼šæ ¹æ¡ç 
            public int TotalSplitTimes { get; set; }
            public string ChainType { get; set; } // "root" æˆ– "branch"
            public List<SplitChainItemDto> SplitChain { get; set; }
        }
        public class SplitChainItemDto
        {
            public DateTime SplitTime { get; set; }
            public string OriginalBarcode { get; set; }
            public string NewBarcode { get; set; }
            public decimal SplitQuantity { get; set; }
            public string Operator { get; set; }
            public bool IsReverted { get; set; }
        }
        #endregion
        #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.OutboundBatchNo);
            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));
        }
        #endregion
        #region æ ¸å¿ƒé€»è¾‘方法
        private async Task<PickingResult> ExecutePickingLogic(
            Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
            Dt_StockInfoDetail stockDetail, decimal actualPickedQty)
        {
@@ -180,86 +1094,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();
@@ -276,7 +1138,13 @@
                Barcode = newBarcode,
                Status = (int)StockStatusEmun.出库锁定,
                SupplyCode = stockDetail.SupplyCode,
                Unit = stockDetail.Unit
                Unit = stockDetail.Unit,
                BarcodeQty=stockDetail.BarcodeQty,
                BarcodeUnit=stockDetail.BarcodeUnit,
                BusinessType=stockDetail.BusinessType,
                InboundOrderRowNo=stockDetail.InboundOrderRowNo,
            };
            await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
@@ -284,27 +1152,44 @@
            stockDetail.StockQuantity -= splitQuantity;
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // åˆ›å»ºæ–°é”å®šä¿¡æ¯
            var newLockInfo = new Dt_OutStockLockInfo
            {
                OrderNo = lockInfo.OrderNo,
                OrderDetailId = lockInfo.OrderDetailId,
                BatchNo = batchNo,
                BatchNo = lockInfo.BatchNo,
                MaterielCode = lockInfo.MaterielCode,
                MaterielName = lockInfo.MaterielName,
                StockId = lockInfo.StockId,
                OrderQuantity = splitQuantity,
                //OriginalQuantity = quantity,
                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,
                Operator = App.User.UserName,
               // OriginalLockQuantity = quantity,
                IsSplitted = 1,
                ParentLockId = lockInfo.Id,
                Operator = App.User.UserName,
                FactoryArea = lockInfo.FactoryArea,
                lineNo = lockInfo.lineNo,
                WarehouseCode = lockInfo.WarehouseCode,
                BarcodeQty = lockInfo.BarcodeQty,
                BarcodeUnit = lockInfo.BarcodeUnit,
            };
            await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
            // æ›´æ–°åŽŸé”å®šä¿¡æ¯
            lockInfo.AssignQuantity -= splitQuantity;
            lockInfo.OrderQuantity -= splitQuantity;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // è®°å½•拆包历史
@@ -313,121 +1198,83 @@
            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 originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
                // æŸ¥æ‰¾æ‹†åŒ…记录和新锁定信息
                var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => x.NewBarcode == newBarcode && x.OrderNo == orderNo && !x.IsReverted)
                    .FirstAsync();
            originalStock.StockQuantity += splitRecord.SplitQty;
            await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
                if (splitRecord == null)
                    return WebResponseContent.Instance.Error("未找到拆包记录");
            // æ¢å¤åŽŸé”å®šä¿¡æ¯
            var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
                var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.CurrentBarcode == newBarcode && x.BatchNo == batchNo)
                    .FirstAsync();
            originalLockInfo.AssignQuantity += splitRecord.SplitQty;
            originalLockInfo.OrderQuantity += splitRecord.SplitQty;
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
                if (newLockInfo == null)
                    return WebResponseContent.Instance.Error("未找到新锁定信息");
            // åˆ é™¤æ–°åº“存明细
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == newLockInfo.CurrentBarcode)
                .ExecuteCommandAsync();
                // æ¢å¤åŽŸåº“å­˜
                var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
            // åˆ é™¤æ–°é”å®šä¿¡æ¯
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(x => x.Id == newLockInfo.Id)
                .ExecuteCommandAsync();
                originalStock.StockQuantity += splitRecord.SplitQty;
                await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
                // æ¢å¤åŽŸé”å®šä¿¡æ¯
                var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
                originalLockInfo.AssignQuantity += splitRecord.SplitQty;
                await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
                // åˆ é™¤æ–°åº“存明细
                await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == newBarcode)
                    .ExecuteCommandAsync();
                // åˆ é™¤æ–°é”å®šä¿¡æ¯
                await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                    .Where(x => x.Id == newLockInfo.Id)
                    .ExecuteCommandAsync();
                // æ ‡è®°æ‹†åŒ…记录为已撤销
                splitRecord.IsReverted = true;
                splitRecord.RevertTime = DateTime.Now;
                splitRecord.Operator = 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}");
            }
            // æ ‡è®°æ‹†åŒ…记录为已撤销
            splitRecord.IsReverted = true;
            splitRecord.RevertTime = DateTime.Now;
            splitRecord.RevertOperator = App.User.UserName;
            await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
        }
        #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();
                // 1. æŸ¥æ‰¾æ‰¹æ¬¡æœªå®Œæˆçš„锁定记录
                var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.BatchNo == batchNo &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
                if (!unfinishedLocks.Any())
                    return WebResponseContent.Instance.Error("该批次没有未完成的锁定记录");
                // 2. é‡Šæ”¾åº“存和锁定记录
                foreach (var lockInfo in unfinishedLocks)
                {
                    await ReleaseLockAndStock(lockInfo);
                }
                // 3. æ›´æ–°æ‰¹æ¬¡çŠ¶æ€
                await UpdateBatchStatusForReturn(batchNo);
                // 4. æ›´æ–°è®¢å•明细的已分配数量
                await UpdateOrderDetailAfterReturn(unfinishedLocks);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("分批回库成功");
                batch.BatchStatus = (int)BatchStatusEnum.已完成;
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"分批回库失败 - OrderNo: {orderNo}, BatchNo: {batchNo}, Error: {ex.Message}");
                return WebResponseContent.Instance.Error($"分批回库失败:{ex.Message}");
            }
            await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
            // æ›´æ–°è®¢å•明细
            orderDetail.OverOutQuantity += pickedQty;
            orderDetail.AllocatedQuantity -= pickedQty;
            await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
            // æ£€æŸ¥è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
        }
        private async Task RevertBatchAndOrderData(Dt_PickingRecord pickingRecord)
        {
            // æ¢å¤æ‰¹æ¬¡å®Œæˆæ•°é‡
            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 +1290,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
                {
                    BatchStatus = (int)BatchStatusEnum.已回库,
                    Operator = App.User.UserName
                })
                .Where(x => x.BatchNo == batchNo)
                .ExecuteCommandAsync();
            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)
            {
                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,10 +1339,10 @@
        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)
        private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal splitQty, string newBarcode)
        {
            var splitHistory = new Dt_SplitPackageRecord
            {
@@ -503,12 +1360,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,53 +1373,66 @@
                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)
                  .ToListAsync();
                .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)
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => new Dt_OutboundOrder { OrderStatus = (int)OutOrderStatusEnum.出库完成 })
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
            var orderStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
            await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                .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
    }
    // æ”¯æŒç±»
    public class SplitResultDto
    {