pan
2025-11-20 246622a6e9c2563bd21d627c21c6012017f0f04e
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -45,6 +45,7 @@
        private readonly IRepository<Dt_Task> _taskRepository;
        private readonly IESSApiService _eSSApiService;
        private readonly IInvokeMESService _invokeMESService;
        private readonly IDailySequenceService _dailySequenceService;
        private readonly ILogger<OutboundPickingService> _logger;
@@ -62,7 +63,10 @@
        };
        public OutboundPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService, IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService, IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService) : base(BaseDal)
        public OutboundPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService,
            IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService,
            IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService,
            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -77,6 +81,7 @@
            _eSSApiService = eSSApiService;
            _logger = logger;
            _invokeMESService = invokeMESService;
            _dailySequenceService = dailySequenceService;
        }
@@ -141,117 +146,6 @@
            }
        }
        public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                  .Where(it => it.OrderNo == orderNo &&
                             it.Status == (int)OutLockStockStatusEnum.出库中 &&
                             it.PalletCode == palletCode &&
                             it.CurrentBarcode == barcode)
                  .FirstAsync();
                if (lockInfo == null)
                {
                    var splitBarcode = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                   .Where(it => it.NewBarcode == barcode && it.Status == 1)
                   .FirstAsync();
                    if (splitBarcode != null)
                    {
                        // é€šè¿‡æ‹†åŒ…条码记录找到对应的出库锁定记录
                        lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                            .Where(it => it.ParentLockId == splitBarcode.OutStockLockInfoId)
                            .FirstAsync();
                        if (lockInfo == null)
                            throw new Exception($"未找到拆包条码{barcode}对应的出库锁定记录");
                    }
                    else
                    {
                        throw new Exception($"条码{barcode}不属于托盘{palletCode}或不存在待分拣记录");
                    }
                }
                if (lockInfo.PalletCode != palletCode)
                    throw new Exception($"条码{barcode}不属于托盘{palletCode}");
                var outorderdetail = _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().First(x => x.Id == lockInfo.OrderDetailId);
                if (outorderdetail != null && lockInfo.AssignQuantity > outorderdetail.OrderQuantity)
                {
                    throw new Exception($"条码{barcode}的出库数量大于订单的数量");
                }
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
                        .FirstAsync();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                // 4. æ›´æ–°åº“å­˜
                stockDetail.StockQuantity -= actualQty;
                stockDetail.OutboundQuantity -= actualQty;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                lockInfo.PickedQty += actualQty;
                lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                var splitBarcodeRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
               .Where(it => it.NewBarcode == barcode)
               .FirstAsync();
                if (splitBarcodeRecord != null)
                {
                    splitBarcodeRecord.Status = 2;
                    await _splitPackageService.Db.Updateable(splitBarcodeRecord).ExecuteCommandAsync();
                }
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
               .SetColumns(it => it.PickedQty == it.PickedQty + actualQty)
               .Where(it => it.Id == lockInfo.OrderDetailId)
               .ExecuteCommandAsync();
                await CheckAndUpdateOrderStatus(orderNo);
                //查询任务表
                var task = _taskRepository.QueryData(x => x.OrderNo == orderNo && x.PalletCode == palletCode).FirstOrDefault();
                // 9. è®°å½•拣选历史
                var pickingHistory = new Dt_PickingRecord
                {
                    FactoryArea = lockInfo.FactoryArea,
                    TaskNo = task?.TaskNum ?? 0,
                    LocationCode = task?.SourceAddress ?? "",
                    StockId = stockDetail.Id,
                    OrderNo = orderNo,
                    OrderDetailId = lockInfo.OrderDetailId,
                    PalletCode = palletCode,
                    Barcode = barcode,
                    MaterielCode = lockInfo.MaterielCode,
                    PickQuantity = lockInfo.AssignQuantity,
                    PickTime = DateTime.Now,
                    Operator = App.User.UserName,
                    OutStockLockId = lockInfo.Id
                };
                await Db.Insertable(pickingHistory).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("拣选确认成功");
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        // æ£€æŸ¥å¹¶æ›´æ–°è®¢å•状态
        private async Task CheckAndUpdateOrderStatus(string orderNo)
        {
@@ -340,13 +234,230 @@
                        }
                    }
                }
                catch (Exception ex) {
                catch (Exception ex)
                {
                    _logger.LogError(" OutboundPickingService  FeedbackOutbound : " + ex.Message);
                }
                 
            }
        }
        public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.Status == (int)OutLockStockStatusEnum.出库中 &&
                               it.PalletCode == palletCode &&
                               it.CurrentBarcode == barcode)
                    .FirstAsync();
                if (lockInfo == null)
                {
                    lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
    .Where(it => it.CurrentBarcode == barcode &&
               it.Status == (int)OutLockStockStatusEnum.出库中)
    .FirstAsync();
                    if (lockInfo == null)
                        throw new Exception($"条码{barcode}不属于托盘{palletCode}或不存在待分拣记录");
                }
                if (lockInfo.PalletCode != palletCode)
                    throw new Exception($"条码{barcode}不属于托盘{palletCode}");
                var outorderdetail = _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().First(x => x.Id == lockInfo.OrderDetailId);
                if (outorderdetail != null && lockInfo.AssignQuantity > outorderdetail.OrderQuantity)
                {
                    throw new Exception($"条码{barcode}的出库数量大于订单的数量");
                }
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
                        .FirstAsync();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                decimal stockQuantity = stockDetail.StockQuantity;
                List<SplitResult> splitResults = new List<SplitResult>();
                if (actualQty < stockQuantity)
                {
                    // æƒ…况1: åˆ†é…æ•°é‡å°äºŽåº“存数量,需要自动拆包
                    // è®¡ç®—剩余库存数量
                    decimal remainingStockQty = stockQuantity - actualQty;
                    // æ›´æ–°åŽŸæ¡ç åº“å­˜ä¸ºå‰©ä½™æ•°é‡
                    stockDetail.StockQuantity = remainingStockQty;
                    stockDetail.OutboundQuantity = remainingStockQty;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    // ç”Ÿæˆæ–°æ¡ç ç”¨äºŽè®°å½•拣选数量(但不创建库存记录)
                    var seq = await _dailySequenceService.GetNextSequenceAsync();
                    string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
                    // ä¸ºæ–°æ¡ç åˆ›å»ºå‡ºåº“锁定信息(用于记录拣选)
                    var newLockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = lockInfo.OrderNo,
                        OrderDetailId = lockInfo.OrderDetailId,
                        BatchNo = lockInfo.BatchNo,
                        MaterielCode = lockInfo.MaterielCode,
                        MaterielName = lockInfo.MaterielName,
                        StockId = lockInfo.StockId,
                        OrderQuantity = actualQty,
                        OriginalQuantity = actualQty,
                        AssignQuantity = actualQty,
                        PickedQty = actualQty,
                        LocationCode = lockInfo.LocationCode,
                        PalletCode = lockInfo.PalletCode,
                        TaskNum = lockInfo.TaskNum,
                        Status = (int)OutLockStockStatusEnum.拣选完成,
                        Unit = lockInfo.Unit,
                        SupplyCode = lockInfo.SupplyCode,
                        OrderType = lockInfo.OrderType,
                        CurrentBarcode = newBarcode,
                        OriginalLockQuantity = actualQty,
                        IsSplitted = 1,
                        ParentLockId = lockInfo.Id
                    };
                    await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
                    // è®°å½•拆包历史(用于追踪)
                    var splitHistory = new Dt_SplitPackageRecord
                    {
                        FactoryArea = lockInfo.FactoryArea,
                        TaskNum = lockInfo.TaskNum,
                        OutStockLockInfoId = lockInfo.Id,
                        StockId = stockDetail.StockId,
                        Operator = App.User.UserName,
                        IsReverted = false,
                        OriginalBarcode = barcode,
                        NewBarcode = newBarcode,
                        SplitQty = actualQty,
                        RemainQuantity = remainingStockQty,
                        MaterielCode = lockInfo.MaterielCode,
                        SplitTime = DateTime.Now,
                        OrderNo = lockInfo.OrderNo,
                        PalletCode = lockInfo.PalletCode,
                        Status = (int)SplitPackageStatusEnum.已拣选
                    };
                    await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
                    // æ›´æ–°åŽŸé”å®šä¿¡æ¯ä¸ºå‰©ä½™åº“å­˜æ•°é‡
                    lockInfo.AssignQuantity = remainingStockQty;
                    lockInfo.PickedQty = 0;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    splitResults.Add(new SplitResult
                    {
                        OriginalBarcode = barcode,
                        NewBarcode = newBarcode,
                        SplitQuantity = actualQty,
                        RemainQuantity = remainingStockQty
                    });
                    // æ›´æ–°æ‹£é€‰è®°å½•中的条码为新条码
                    barcode = newBarcode;
                    lockInfo = newLockInfo;
                }
                else if (actualQty == stockQuantity)
                {
                    // æƒ…况2: åˆ†é…æ•°é‡ç­‰äºŽåº“存数量,整包出库
                    stockDetail.StockQuantity = 0;
                    stockDetail.OutboundQuantity = 0;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    lockInfo.PickedQty += actualQty;
                    lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                }
                else
                {
                    // æƒ…况3: åˆ†é…æ•°é‡å¤§äºŽåº“存数量,库存整包出库
                    // æ•´åŒ…出库当前库存
                    decimal stockOutQty = stockQuantity;
                    stockDetail.StockQuantity = 0;
                    stockDetail.OutboundQuantity = 0;
                    await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                    // è®¡ç®—剩余分配数量
                    decimal remainingAssignQty = actualQty - stockQuantity;
                    // æ›´æ–°é”å®šä¿¡æ¯ï¼ˆåªå®Œæˆåº“存部分)
                    lockInfo.PickedQty += stockOutQty;
                    lockInfo.AssignQuantity = remainingAssignQty;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    var _relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
        .Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode)
        .Where(it => !it.IsReverted)
        .ToListAsync();
                    foreach (var record in _relatedSplitRecords)
                    {
                        record.Status = (int)SplitPackageStatusEnum.已拣选;
                        await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                    }
                }
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => it.PickedQty == it.PickedQty + actualQty)
                    .Where(it => it.Id == lockInfo.OrderDetailId)
                    .ExecuteCommandAsync();
                await CheckAndUpdateOrderStatus(orderNo);
                // æŸ¥è¯¢ä»»åŠ¡è¡¨
                var task = _taskRepository.QueryData(x => x.OrderNo == orderNo && x.PalletCode == palletCode).FirstOrDefault();
                // è®°å½•拣选历史
                var pickingHistory = new Dt_PickingRecord
                {
                    FactoryArea = lockInfo.FactoryArea,
                    TaskNo = task?.TaskNum ?? 0,
                    LocationCode = task?.SourceAddress ?? "",
                    StockId = stockDetail.Id,
                    OrderNo = orderNo,
                    OrderDetailId = lockInfo.OrderDetailId,
                    PalletCode = palletCode,
                    Barcode = barcode,
                    MaterielCode = lockInfo.MaterielCode,
                    PickQuantity = actualQty,
                    PickTime = DateTime.Now,
                    Operator = App.User.UserName,
                    OutStockLockId = lockInfo.Id
                };
                await Db.Insertable(pickingHistory).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                // å¦‚果有拆包结果,返回拆包信息
                if (splitResults.Any())
                {
                    return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = splitResults });
                }
                return WebResponseContent.Instance.OK("拣选确认成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        /// <summary>
        /// å›žåº“操作  
@@ -356,13 +467,14 @@
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                //  èŽ·å–æ‰€æœ‰æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•ï¼ŒåŒ…æ‹¬æ‹†åŒ…äº§ç”Ÿçš„è®°å½•
                var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo && it.Status == 1)
                    .Where(it => it.OrderNo == orderNo && it.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
                var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
                var tasks = new List<Dt_Task>();
@@ -378,24 +490,66 @@
                //  æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–非出库货物(库存货物)
                var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(it => it.StockId == stockinfo.Id && (it.Status == StockStatusEmun.入库确认.ObjToInt() || it.Status == StockStatusEmun.入库完成.ObjToInt() || it.Status == StockStatusEmun.出库锁定.ObjToInt()))
                    .Where(it => it.StockId == stockinfo.Id &&
                                (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
                                 it.Status == StockStatusEmun.入库完成.ObjToInt() ||
                                 it.Status == StockStatusEmun.出库锁定.ObjToInt()))
                    .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // æœªå®Œå…¨å‡ºåº“çš„
                    .ToListAsync();
                //  å¦‚果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物)
                if (!remainingLocks.Any() && !palletStockGoods.Any())
                // æ£€æŸ¥æ‹†åŒ…记录,找出需要回库的条码
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                    .ToListAsync();
                // è®¡ç®—需要回库的拆包条码
                var splitBarcodesToReturn = new List<string>();
                foreach (var splitRecord in splitRecords)
                {
                    //是否自动回库,把之前出库的任务删除,然后组个空盘入库。
                    return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
                    // æ£€æŸ¥åŽŸæ¡ç æ˜¯å¦è¿˜æœ‰åº“å­˜éœ€è¦å›žåº“
                    var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockinfo.Id)
                        .FirstAsync();
                    if (originalStock != null && originalStock.StockQuantity > 0)
                    {
                        splitBarcodesToReturn.Add(splitRecord.OriginalBarcode);
                }
                    // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦è¿˜æœ‰åº“存需要回库
                    var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockinfo.Id)
                        .FirstAsync();
                    if (newStock != null && newStock.StockQuantity > 0)
                    {
                        splitBarcodesToReturn.Add(splitRecord.NewBarcode);
                    }
                }
                // å¦‚果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物,也无拆包剩余货物)
                if (!remainingLocks.Any() && !palletStockGoods.Any() && !splitBarcodesToReturn.Any())
                {
                    // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
                    var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
                        .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
                    if (allPicked)
                    {
                        return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
                    }
                    else
                    {
                        return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
                    }
                }
                var firstlocation = _locationInfoService.Db.Queryable<Dt_LocationInfo>().First(x => x.LocationCode == task.SourceAddress);
                decimal totalReturnQty = 0;
                var hasRemainingLocks = remainingLocks.Any(x => x.PalletCode == palletCode);
                // æƒ…况1:处理未分拣的出库锁定记录
                if (hasRemainingLocks)
                if (remainingLocks.Any(x => x.PalletCode == palletCode))
                {
                    var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
                    totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
@@ -408,20 +562,11 @@
                        // æ›´æ–°å‡ºåº“锁定记录状态
                        var lockIds = palletLocks.Select(x => x.Id).ToList();
                        await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                            .SetColumns(it => new Dt_OutStockLockInfo { Status = OutLockStockStatusEnum.回库中.ObjToInt() })
                            .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.回库中 })
                            .Where(it => lockIds.Contains(it.Id))
                            .ExecuteCommandAsync();
                        // æ›´æ–°æ‹†åŒ…条码记录状态
                        var splitBarcodes = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                            .Where(it => lockIds.Contains(it.OutStockLockInfoId))
                            .ToListAsync();
                        foreach (var splitBarcode in splitBarcodes)
                        {
                            splitBarcode.Status = 3;
                            await _splitPackageService.Db.Updateable(splitBarcode).ExecuteCommandAsync();
                        }
                        // å¤„理库存记录
                        foreach (var lockInfo in palletLocks)
@@ -436,13 +581,9 @@
                            if (existingStock != null)
                            {
                                // åº“存记录存在,恢复锁定数量
                                await _stockInfoDetailService.Db.Updateable<Dt_StockInfoDetail>()
                                    .SetColumns(it => new Dt_StockInfoDetail
                                    {
                                        OutboundQuantity = it.OutboundQuantity - returnQty
                                    })
                                    .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                                    .ExecuteCommandAsync();
                                existingStock.OutboundQuantity = 0;
                                await _stockInfoDetailService.Db.Updateable(existingStock).ExecuteCommandAsync();
                            }
                            else
                            {
@@ -451,14 +592,17 @@
                                {
                                    StockId = lockInfo.StockId,
                                    MaterielCode = lockInfo.MaterielCode,
                                    MaterielName = lockInfo.MaterielName,
                                    OrderNo = lockInfo.OrderNo,
                                    BatchNo = lockInfo.BatchNo,
                                    StockQuantity = returnQty,
                                    OutboundQuantity = 0,
                                    Barcode = lockInfo.CurrentBarcode,
                                    InboundOrderRowNo = "0",
                                    Status = StockStatusEmun.入库确认.ObjToInt(),
                                    InboundOrderRowNo = "",
                                    Status = StockStatusEmun.入库完成.ObjToInt(),
                                    SupplyCode = lockInfo.SupplyCode,
                                    WarehouseCode = lockInfo.WarehouseCode,
                                    Unit = lockInfo.Unit
                                };
                                await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                            }
@@ -469,18 +613,69 @@
                    }
                }
                // æƒ…况2:出库货物已分拣完,但托盘上还有其他库存货物需要回库
                if (!hasRemainingLocks && palletStockGoods.Any())
                // æƒ…况2:处理拆包剩余的库存货物
                if (splitBarcodesToReturn.Any())
                {
                    // åˆ†é…æ–°è´§ä½
                    var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                    decimal splitReturnQty = 0;
                    // åˆ›å»ºå›žåº“任务
                    CreateReturnTask(tasks, task, palletCode, newLocation);
                    foreach (var barcode in splitBarcodesToReturn)
                    {
                        var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                            .Where(it => it.Barcode == barcode && it.StockId == stockinfo.Id)
                            .FirstAsync();
                    totalReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
                        if (stockDetail != null && stockDetail.StockQuantity > 0)
                        {
                            splitReturnQty += stockDetail.StockQuantity;
                            // æ¢å¤åº“存状态为入库完成
                            stockDetail.OutboundQuantity = 0;
                            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                        }
                }
                    totalReturnQty += splitReturnQty;
                    // å¦‚果没有创建任务,创建回库任务
                    if (!tasks.Any())
                    {
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                // æƒ…况3:出库货物已分拣完,但托盘上还有其他库存货物需要回库
                if (palletStockGoods.Any() && !remainingLocks.Any(x => x.PalletCode == palletCode))
                {
                    decimal otherReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
                    totalReturnQty += otherReturnQty;
                    // æ›´æ–°è¿™äº›åº“存货物的状态
                    foreach (var stockGood in palletStockGoods)
                    {
                        stockGood.OutboundQuantity = 0;
                        await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
                    }
                    // å¦‚果没有创建任务,创建回库任务
                    if (!tasks.Any())
                    {
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                var allSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
                    .ToListAsync();
                foreach (var record in allSplitRecords)
                {
                    record.Status = (int)SplitPackageStatusEnum.已回库;
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                // ä¿å­˜ä»»åŠ¡ ç»™ESS下发任务
                if (tasks.Any())
                {
@@ -489,6 +684,7 @@
                        await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
                        var targetAddress = task.TargetAddress;
                        _taskRepository.DeleteData(task);
                        // ç»™ ESS æµåŠ¨ä¿¡å·å’Œåˆ›å»ºä»»åŠ¡
                        try
                        {
@@ -497,6 +693,7 @@
                                slotCode = movestations[targetAddress],
                                containerCode = palletCode
                            });
                            if (result)
                            {
                                TaskModel esstask = new TaskModel()
@@ -522,34 +719,35 @@
                                    }
                                };
                                var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
                                _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž:  " + resulttask);
                            }
                        }
                        catch (Exception ex)
                        {
                            _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž catch err:  " + ex.Message);
                        }
                        _unitOfWorkManage.CommitTran();
                        return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
                    }
                    catch (Exception ex)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
                    }
                }
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error("未创建任何回库任务");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
            }
        }
        /// <summary>
        /// åˆ›å»ºå›žåº“任务
@@ -610,56 +808,306 @@
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾æ‹£é€‰è®°å½•
                var outStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.OrderNo == orderNo &&
                                   x.PalletCode == palletCode &&
                                   x.CurrentBarcode == barcode &&
                                   x.Status == 6)
                var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
                    .Where(it => it.OrderNo == orderNo &&
                               it.PalletCode == palletCode &&
                               it.Barcode == barcode)
                    .OrderByDescending(it => it.PickTime)
                        .FirstAsync();
                if (outStockInfo == null)
                    return WebResponseContent.Instance.Error("未找到已拣选记录");
                if (pickingRecord == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选记录");
                // è¿˜åŽŸå‡ºåº“è¯¦æƒ…çŠ¶æ€
                outStockInfo.PickedQty = 0;
                outStockInfo.Status = 1;
                await _outStockLockInfoService.Db.Updateable(outStockInfo).ExecuteCommandAsync();
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                       .Where(x => x.Barcode == barcode && x.StockId == outStockInfo.StockId)
                // æŸ¥æ‰¾å‡ºåº“锁定信息
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.Id == pickingRecord.OutStockLockId)
                       .FirstAsync();
                stockDetail.StockQuantity += outStockInfo.AssignQuantity;
                stockDetail.OutboundQuantity += outStockInfo.AssignQuantity;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的出库锁定信息");
                //检查是否可以取消(状态必须是拣选完成)
                if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                    return WebResponseContent.Instance.Error("当前状态不允许取消分拣");
                // è¿˜åŽŸå‡ºåº“å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == outStockInfo.OrderDetailId)
                    .FirstAsync();
                decimal cancelQty = pickingRecord.PickQuantity;
                orderDetail.OverOutQuantity -= outStockInfo.AssignQuantity;
                orderDetail.PickedQty = 0;
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                // æ£€æŸ¥æ‹†åŒ…链关系
                var splitChain = await GetSplitChain(barcode);
                // åˆ é™¤æ‹£é€‰åŽ†å²
                await Db.Deleteable<Dt_PickingRecord>()
                    .Where(x => x.OutStockLockId == outStockInfo.Id)
                    .ExecuteCommandAsync();
                if (splitChain.Any())
                {
                    // æƒ…况A:处理拆包链的取消(多次手动拆包)
                    await HandleSplitChainCancel(orderNo, palletCode, barcode, cancelQty, lockInfo, pickingRecord, splitChain);
                }
                else
                {
                    // æƒ…况B:处理普通条码的取消
                    await HandleNormalBarcodeCancel(orderNo, palletCode, barcode, cancelQty, lockInfo, pickingRecord);
                }
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取消拣选成功");
                return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{cancelQty}");
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"取消拣选失败:{ex.Message}");
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
            }
        }
        /// <summary>
        /// èŽ·å–æ‹†åŒ…é“¾ï¼ˆä»Žå½“å‰æ¡ç è¿½æº¯åˆ°åŽŸå§‹æ¡ç ï¼‰
        /// </summary>
        // åœ¨ GetSplitChain æ–¹æ³•中添加更严格的验证
        private async Task<List<SplitChainItem>> GetSplitChain(string currentBarcode)
        {
            var chain = new List<SplitChainItem>();
            var visited = new HashSet<string>();
            string current = currentBarcode;
            int maxDepth = 10; // é˜²æ­¢æ— é™å¾ªçޝ
            while (!string.IsNullOrEmpty(current) && maxDepth > 0)
            {
                maxDepth--;
                if (visited.Contains(current))
                {
                    _logger.LogWarning($"检测到循环引用在拆包链中: {current}");
                    break;
                }
                visited.Add(current);
                var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(it => it.NewBarcode == current && !it.IsReverted)
                    .FirstAsync();
                if (splitRecord == null)
                    break;
                // éªŒè¯æ‹†åŒ…记录的完整性
                if (string.IsNullOrEmpty(splitRecord.OriginalBarcode))
                {
                    _logger.LogError($"拆包记录 {splitRecord.Id} ç¼ºå°‘原始条码");
                    break;
                }
                var item = new SplitChainItem
                {
                    SplitRecord = splitRecord,
                    OriginalBarcode = splitRecord.OriginalBarcode,
                    NewBarcode = splitRecord.NewBarcode,
                    SplitQuantity = splitRecord.SplitQty
                };
                chain.Add(item);
                current = splitRecord.OriginalBarcode;
            }
            if (maxDepth <= 0)
            {
                _logger.LogWarning($"拆包链追溯达到最大深度: {currentBarcode}");
            }
            chain.Reverse();
            return chain;
        }
        /// <summary>
        /// å¤„理拆包链的取消分拣
        /// </summary>
        private async Task HandleSplitChainCancel(string orderNo, string palletCode, string barcode,
            decimal cancelQty, Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, List<SplitChainItem> splitChain)
        {
            if (!splitChain.Any())
                return;
            //  æ‰¾åˆ°åŽŸå§‹æ¡ç ï¼ˆé“¾çš„ç¬¬ä¸€ä¸ªï¼‰
            var originalSplitItem = splitChain.First();
            var originalBarcode = originalSplitItem.OriginalBarcode;
            // æŸ¥æ‰¾åŽŸå§‹æ¡ç çš„é”å®šä¿¡æ¯å’Œåº“å­˜
            var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.CurrentBarcode == originalBarcode && it.Status == (int)OutLockStockStatusEnum.出库中)
                .FirstAsync();
            var originalStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == originalBarcode && it.StockId == originalLockInfo.StockId)
                .FirstAsync();
            if (originalLockInfo == null || originalStockDetail == null)
                throw new Exception("未找到原始条码的锁定信息或库存信息");
            // æ¢å¤åŽŸå§‹æ¡ç åº“å­˜ï¼ˆå°†å–æ¶ˆçš„æ•°é‡åŠ å›žåŽ»ï¼‰
            originalStockDetail.StockQuantity += cancelQty;
            originalStockDetail.OutboundQuantity += cancelQty;
            await _stockInfoDetailService.Db.Updateable(originalStockDetail).ExecuteCommandAsync();
            // æ¢å¤åŽŸå§‹æ¡ç é”å®šä¿¡æ¯
            originalLockInfo.AssignQuantity += cancelQty;
            await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
            //  åˆ é™¤æ‹†åŒ…链中所有新条码的锁定信息和库存记录
            var allNewBarcodes = splitChain.Select(x => x.NewBarcode).ToList();
            // åˆ é™¤é”å®šä¿¡æ¯
            await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                .Where(it => allNewBarcodes.Contains(it.CurrentBarcode))
                .ExecuteCommandAsync();
            // åˆ é™¤åº“存记录(只删除拆包产生的新条码库存,保留原始条码)
            await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                .Where(it => allNewBarcodes.Contains(it.Barcode) && it.Barcode != originalBarcode)
                .ExecuteCommandAsync();
            // æ›´æ–°æ‹†åŒ…链中所有拆包记录状态为已拆包
            foreach (var chainItem in splitChain)
            {
                chainItem.SplitRecord.Status = (int)SplitPackageStatusEnum.已拆包;
                await _splitPackageService.Db.Updateable(chainItem.SplitRecord).ExecuteCommandAsync();
            }
            //  æ¢å¤è®¢å•明细拣选数量(使用原始锁定信息的订单明细ID)
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => it.PickedQty == it.PickedQty - cancelQty)
                .Where(it => it.Id == originalLockInfo.OrderDetailId)
                .ExecuteCommandAsync();
            //   æ¢å¤è®¢å•状态
            await CheckAndRevertOrderStatus(orderNo);
            //  åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>()
                .Where(it => it.Id == pickingRecord.Id)
                .ExecuteCommandAsync();
            ////  è®°å½•取消操作历史
            //await RecordCancelHistory(orderNo, palletCode, barcode, cancelQty, pickingRecord.Id,
            //    lockInfo.MaterielCode, "取消拆包链分拣");
        }
        /// <summary>
        /// å¤„理普通条码的取消分拣
        /// </summary>
        private async Task HandleNormalBarcodeCancel(string orderNo, string palletCode, string barcode,
            decimal cancelQty, Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord)
        {
            // 1. æŸ¥æ‰¾åº“存信息
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == barcode && it.StockId == lockInfo.StockId)
                .FirstAsync();
            if (stockDetail == null)
                throw new Exception("未找到对应的库存信息");
            // 2. æ¢å¤åº“存数量
            if (stockDetail.StockQuantity == 0)
            {
                // æ•´åŒ…出库的情况
                stockDetail.StockQuantity = cancelQty;
                stockDetail.OutboundQuantity = cancelQty;
            }
            else
            {
                // éƒ¨åˆ†å‡ºåº“的情况
                stockDetail.StockQuantity += cancelQty;
                stockDetail.OutboundQuantity += cancelQty;
            }
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 3. æ¢å¤é”å®šä¿¡æ¯çŠ¶æ€
            lockInfo.AssignQuantity += cancelQty;
            lockInfo.PickedQty -= cancelQty;
            if (lockInfo.PickedQty == 0)
            {
                lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
            }
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 4. å¤„理相关的拆包记录状态恢复
            var relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                .Where(it => it.OriginalBarcode == barcode &&
                           it.Status == (int)SplitPackageStatusEnum.已拣选)
                .ToListAsync();
            foreach (var record in relatedSplitRecords)
            {
                record.Status = (int)SplitPackageStatusEnum.已拆包;
                await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
            }
            // 5. æ¢å¤è®¢å•明细的拣选数量
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => it.PickedQty == it.PickedQty - cancelQty)
                .Where(it => it.Id == lockInfo.OrderDetailId)
                .ExecuteCommandAsync();
            // 6. æ¢å¤è®¢å•状态
            await CheckAndRevertOrderStatus(orderNo);
            // 7. åˆ é™¤æ‹£é€‰è®°å½•
            await Db.Deleteable<Dt_PickingRecord>().Where(it => it.Id == pickingRecord.Id).ExecuteCommandAsync();
            //// 8. è®°å½•取消操作历史
            //await RecordCancelHistory(orderNo, palletCode, barcode, cancelQty, pickingRecord.Id,
            //    lockInfo.MaterielCode, "取消分拣");
        }
        /// <summary>
        /// æ£€æŸ¥å¹¶æ¢å¤è®¢å•状态
        /// </summary>
        private async Task CheckAndRevertOrderStatus(string orderNo)
        {
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
                .FirstAsync();
            if (order != null && order.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt())
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => x.OrderStatus == OutOrderStatusEnum.出库中.ObjToInt())
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// è®°å½•取消操作历史
        /// </summary>
        private async Task RecordCancelHistory(string orderNo, string palletCode, string barcode,
            decimal cancelQty, int pickingRecordId, string materielCode, string reason)
        {
            //var cancelHistory = new Dt_PickingCancelRecord
            //{
            //    OrderNo = orderNo,
            //    PalletCode = palletCode,
            //    Barcode = barcode,
            //    CancelQuantity = cancelQty,
            //    CancelTime = DateTime.Now,
            //    Operator = App.User.UserName,
            //    OriginalPickingRecordId = pickingRecordId,
            //    MaterielCode = materielCode,
            //    Reason = reason
            //};
            //await Db.Insertable(cancelHistory).ExecuteCommandAsync();
        }
        /// <summary>
        /// æ‹†åŒ…链项
        /// </summary>
        public class SplitChainItem
        {
            public Dt_SplitPackageRecord SplitRecord { get; set; }
            public string OriginalBarcode { get; set; }
            public string NewBarcode { get; set; }
            public decimal SplitQuantity { get; set; }
        }
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
        {