pan
2025-11-19 c84db706e8c8d82a96bb4b4c18c243a42b2976c1
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -1,4 +1,9 @@
using System;
using Dm.filter;
using MailKit.Search;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,9 +11,13 @@
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.OrderEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_DTO.Outbound;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
@@ -31,10 +40,29 @@
        private readonly IStockInfoDetailService _stockInfoDetailService;
        private readonly ILocationInfoService _locationInfoService;
        private readonly IOutboundOrderDetailService _outboundOrderDetailService;
        private readonly IOutboundOrderService _outboundOrderService;
        private readonly ISplitPackageService _splitPackageService;
        private readonly IRepository<Dt_Task> _taskRepository;
        private readonly IESSApiService _eSSApiService;
        private readonly IInvokeMESService _invokeMESService;
        public OutboundPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService, IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService) : base(BaseDal)
        private readonly ILogger<OutboundPickingService> _logger;
        private Dictionary<string, string> stations = new Dictionary<string, string>
        {
            {"2-1","2-9" },
            {"3-1","3-9" },
        };
        private Dictionary<string, string> movestations = new Dictionary<string, string>
        {
            {"2-1","2-5" },
            {"3-1","3-5" },
        };
        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)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -44,117 +72,655 @@
            _locationInfoService = locationInfoService;
            _outboundOrderDetailService = outboundOrderDetailService;
            _splitPackageService = splitPackageService;
            _outboundOrderService = outboundOrderService;
            _taskRepository = taskRepository;
            _eSSApiService = eSSApiService;
            _logger = logger;
            _invokeMESService = invokeMESService;
        }
        /// <summary>
        /// æ‰«ç æ‹£é€‰ç¡®è®¤ - ç®€åŒ–版本
        /// åªå¤„理实际拣选的库存扣减
        /// </summary>
        public async Task<WebResponseContent> ConfirmPicking(PickingConfirmRequest request)
        #region æŸ¥è¯¢å‡ºåº“详情列表
        public async Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo)
        {
            var locks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(t => t.OrderNo == orderNo)
                .ToListAsync();
            return locks.Select(t => new OutStockLockListResp
            {
                Id = t.Id,
                // TaskNum = t.TaskNum,
                PalletCode = t.PalletCode,
                CurrentBarcode = t.CurrentBarcode,
                AssignQuantity = t.AssignQuantity,
                PickedQty = t.PickedQty,
                Status = t.Status,
                //  IsSplitted = t.IsSplitted
            }).ToList();
        }
        #endregion
        public async Task<WebResponseContent> ValidateBarcode(string barcode)
        {
            try
            {
                if (string.IsNullOrEmpty(barcode))
                {
                    return WebResponseContent.Instance.Error("条码不能为空");
                }
                // æ ¹æ®æ¡ç æŸ¥è¯¢åº“存明细
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Includes(x => x.StockInfo)
                    .Where(x => x.Barcode == barcode)
                    .FirstAsync();
                if (stockDetail == null)
                {
                    return WebResponseContent.Instance.Error("条码不存在");
                }
                var result = new
                {
                    Barcode = barcode,
                    MaterielCode = stockDetail.MaterielCode,
                    BatchNo = stockDetail.BatchNo,
                    AvailableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity,
                    LocationCode = stockDetail.StockInfo?.LocationCode,
                    PalletCode = stockDetail.StockInfo?.PalletCode
                };
                return WebResponseContent.Instance.OK(null, result);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"条码验证失败: {ex.Message}");
            }
        }
        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();
                // 1. éªŒè¯æ¡ç æœ‰æ•ˆæ€§
                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 == request.Barcode && x.MaterielCode == request.MaterielCode)
                    .FirstAsync();
                        .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
                        .FirstAsync();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                // 2. æ£€æŸ¥åº“存可用数量
                decimal availableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity;
                if (request.PickQuantity > availableQuantity)
                    return WebResponseContent.Instance.Error($"拣选数量超过可用库存,可用数量:{availableQuantity}");
                // 3. æŸ¥æ‰¾ç›¸å…³çš„出库锁定信息(支持拆包后的新条码)
                var lockInfo = await FindLockInfoByBarcode(request.OrderDetailId, request.Barcode, request.MaterielCode);
                decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到相关的出库锁定信息");
                // 4. æ›´æ–°åº“å­˜
                stockDetail.StockQuantity -= actualQty;
                stockDetail.OutboundQuantity -= actualQty;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                // 4. æ£€æŸ¥é”å®šæ•°é‡
                decimal remainingLockQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
                if (request.PickQuantity > remainingLockQuantity)
                    return WebResponseContent.Instance.Error($"拣选数量超过锁定数量,剩余可拣选:{remainingLockQuantity}");
                // 5. æ›´æ–°é”å®šä¿¡æ¯çš„已拣选数量
                lockInfo.PickedQty += request.PickQuantity;
                lockInfo.PickedQty += actualQty;
                lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                // 6. æ›´æ–°åº“存出库数量 - å®žé™…减少库存
                stockDetail.OutboundQuantity += request.PickQuantity;
                await _stockInfoService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                var splitBarcodeRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
               .Where(it => it.NewBarcode == barcode)
               .FirstAsync();
                // 7. æ›´æ–°å‡ºåº“单明细
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == request.OrderDetailId)
                    .FirstAsync();
                orderDetail.OverOutQuantity += request.PickQuantity;
                orderDetail.LockQuantity -= request.PickQuantity;
                // æ£€æŸ¥æ˜¯å¦å®Œæˆå‡ºåº“
                if (Math.Abs(orderDetail.OverOutQuantity - orderDetail.OrderQuantity) < 0.001m)
                if (splitBarcodeRecord != null)
                {
                    orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                    orderDetail.LockQuantity = 0;
                    // æ›´æ–°ç›¸å…³çš„锁定信息状态为已出库
                    var relatedLockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.OrderDetailId == request.OrderDetailId &&
                                   x.Status == (int)OutLockStockStatusEnum.出库中)
                        .ToListAsync();
                    foreach (var relatedLock in relatedLockInfos)
                    {
                        relatedLock.Status = (int)OutLockStockStatusEnum.已出库;
                    }
                    await _outStockLockInfoService.Db.Updateable(relatedLockInfos).ExecuteCommandAsync();
                    splitBarcodeRecord.Status = 2;
                    await _splitPackageService.Db.Updateable(splitBarcodeRecord).ExecuteCommandAsync();
                }
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
               .SetColumns(it => it.PickedQty == it.PickedQty + actualQty)
               .Where(it => it.Id == lockInfo.OrderDetailId)
               .ExecuteCommandAsync();
                // 8. è®°å½•拣选历史
                var pickHistory = new Dt_PickingRecord
                await CheckAndUpdateOrderStatus(orderNo);
                //查询任务表
                var task = _taskRepository.QueryData(x => x.OrderNo == orderNo && x.PalletCode == palletCode).FirstOrDefault();
                // 9. è®°å½•拣选历史
                var pickingHistory = new Dt_PickingRecord
                {
                    OrderDetailId = request.OrderDetailId,
                    Barcode = request.Barcode,
                    PickQuantity = request.PickQuantity,
                    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,
                    LocationCode = request.LocationCode,
                    StockId = stockDetail.StockId
                    Operator = App.User.UserName,
                    OutStockLockId = lockInfo.Id
                };
                await Db.Insertable(pickHistory).ExecuteCommandAsync();
                await Db.Insertable(pickingHistory).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("拣选确认成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"拣选确认失败: {ex.Message}");
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        // æ£€æŸ¥å¹¶æ›´æ–°è®¢å•状态
        private async Task CheckAndUpdateOrderStatus(string orderNo)
        {
            var orderDetails = _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                      .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id) // å…³è”条件:父表 Id = å­è¡¨ OrderId
                      .Where((o, item) => item.OrderNo == orderNo) // è¿‡æ»¤çˆ¶è¡¨ OrderNo
                      .Select((o, item) => o) // åªè¿”回子表数据
                      .ToList();
            //var orderDetails = await _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
            //    .Where(x => x.OrderId == orderNo.ObjToInt())
            //    .ToListAsync();
            bool allCompleted = true;
            foreach (var detail in orderDetails)
            {
                if (detail.OverOutQuantity < detail.NeedOutQuantity)
                {
                    allCompleted = false;
                    break;
                }
            }
            if (allCompleted)
            {
                try
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == 2) // å·²å®Œæˆ
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    var outboundOrder = _stockInfoService.Db.Queryable<Dt_OutboundOrder>().First(x => x.OrderNo == orderNo);
                    if (outboundOrder != null && outboundOrder.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt())
                    {
                        if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt().ObjToInt())//调拨出库
                        {
                        }
                        else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt()) //重检出库
                        {
                        }
                        else
                        {
                            var feedmodel = new FeedbackOutboundRequestModel
                            {
                                reqCode = Guid.NewGuid().ToString(),
                                reqTime = DateTime.Now.ToString(),
                                business_type = outboundOrder.BusinessType,
                                factoryArea = outboundOrder.FactoryArea,
                                operationType = 1,
                                Operator = outboundOrder.Operator,
                                orderNo = outboundOrder.UpperOrderNo,
                                status = outboundOrder.OrderStatus,
                                details = new List<FeedbackOutboundDetailsModel>()
                            };
                            var lists = _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>().Where(x => x.OrderNo == orderNo).ToList();
                            var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                               .Select(group => new FeedbackOutboundDetailsModel
                               {
                                   materialCode = group.Key.MaterielCode,
                                   lineNo = group.Key.lineNo,
                                   warehouseCode = group.Key.WarehouseCode,
                                   currentDeliveryQty = group.Sum(x => x.OrderQuantity),
                                   // warehouseCode= "1072",
                                   unit = group.Key.Unit,
                                   barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                                   {
                                       barcode = row.CurrentBarcode,
                                       supplyCode = row.SupplyCode,
                                       batchNo = row.BatchNo,
                                       unit = row.Unit,
                                       qty = row.AssignQuantity
                                   }).ToList()
                               }).ToList();
                            feedmodel.details = groupedData;
                            _invokeMESService.FeedbackOutbound(feedmodel);
                        }
                    }
                }
                catch (Exception ex) {
                    _logger.LogError(" OutboundPickingService  FeedbackOutbound : " + ex.Message);
                }
            }
        }
        /// <summary>
        /// å›žåº“操作
        /// </summary>
        public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
        {
            try
            {
                //  èŽ·å–æ‰€æœ‰æœªåˆ†æ‹£çš„å‡ºåº“é”å®šè®°å½•ï¼ŒåŒ…æ‹¬æ‹†åŒ…äº§ç”Ÿçš„è®°å½•
                var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(it => it.OrderNo == orderNo && it.Status == 1)
                    .ToListAsync();
                var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
                var tasks = new List<Dt_Task>();
                // æŸ¥è¯¢ä»»åŠ¡è¡¨
                var task = remainingLocks.Any()
                    ? _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault()
                    : _taskRepository.QueryData(x => x.PalletCode == palletCode).FirstOrDefault();
                if (task == null)
                {
                    return WebResponseContent.Instance.Error("未找到对应的任务信息");
                }
                //  æ£€æŸ¥æ‰˜ç›˜ä¸Šæ˜¯å¦æœ‰å…¶ä»–非出库货物(库存货物)
                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.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // æœªå®Œå…¨å‡ºåº“çš„
                    .ToListAsync();
                //  å¦‚果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物)
                if (!remainingLocks.Any() && !palletStockGoods.Any())
                {
                    //是否自动回库,把之前出库的任务删除,然后组个空盘入库。
                    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)
                {
                    var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
                    totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
                    if (totalReturnQty > 0)
                    {
                        // åˆ†é…æ–°è´§ä½
                        var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                        // æ›´æ–°å‡ºåº“锁定记录状态
                        var lockIds = palletLocks.Select(x => x.Id).ToList();
                        await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                            .SetColumns(it => new Dt_OutStockLockInfo { Status = OutLockStockStatusEnum.回库中.ObjToInt() })
                            .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)
                        {
                            decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
                            // æ£€æŸ¥åº“存记录是否存在
                            var existingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                                .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
                                .FirstAsync();
                            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();
                            }
                            else
                            {
                                // åº“存记录不存在(可能是拆包产生的新条码),创建新的库存记录
                                var newStockDetail = new Dt_StockInfoDetail
                                {
                                    StockId = lockInfo.StockId,
                                    MaterielCode = lockInfo.MaterielCode,
                                    OrderNo = lockInfo.OrderNo,
                                    BatchNo = lockInfo.BatchNo,
                                    StockQuantity = returnQty,
                                    OutboundQuantity = 0,
                                    Barcode = lockInfo.CurrentBarcode,
                                    InboundOrderRowNo = "0",
                                    Status = StockStatusEmun.入库确认.ObjToInt(),
                                };
                                await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                            }
                        }
                        // åˆ›å»ºå›žåº“任务
                        CreateReturnTask(tasks, task, palletCode, newLocation);
                    }
                }
                // æƒ…况2:出库货物已分拣完,但托盘上还有其他库存货物需要回库
                if (!hasRemainingLocks && palletStockGoods.Any())
                {
                    // åˆ†é…æ–°è´§ä½
                    var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
                    // åˆ›å»ºå›žåº“任务
                    CreateReturnTask(tasks, task, palletCode, newLocation);
                    totalReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
                }
                // ä¿å­˜ä»»åŠ¡ ç»™ESS下发任务
                if (tasks.Any())
                {
                    try
                    {
                        await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
                        var targetAddress = task.TargetAddress;
                        _taskRepository.DeleteData(task);
                        // ç»™ ESS æµåŠ¨ä¿¡å·å’Œåˆ›å»ºä»»åŠ¡
                        try
                        {
                            var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                            {
                                slotCode = movestations[targetAddress],
                                containerCode = palletCode
                            });
                            if (result)
                            {
                                TaskModel esstask = new TaskModel()
                                {
                                    taskType = "putaway",
                                    taskGroupCode = "",
                                    groupPriority = 0,
                                    tasks = new List<TasksType>
                                    {
                                        new()
                                        {
                                            taskCode = tasks.First().TaskNum.ToString(),
                                            taskPriority = 0,
                                            taskDescribe = new TaskDescribeType {
                                                containerCode = palletCode,
                                                containerType = "CT_KUBOT_STANDARD",
                                                fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
                                                toStationCode = "",
                                                toLocationCode = tasks.First().TargetAddress,
                                                deadline = 0, storageTag = ""
                                            }
                                        }
                                    }
                                };
                                var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
                                _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž:  " + resulttask);
                            }
                        }
                        catch (Exception ex)
                        {
                            _logger.LogInformation("ReturnRemaining åˆ›å»ºä»»åŠ¡è¿”å›ž catch err:  " + ex.Message);
                        }
                        return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
                    }
                    catch (Exception ex)
                    {
                        return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
                    }
                }
                return WebResponseContent.Instance.Error("未创建任何回库任务");
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
            }
        }
        /// <summary>
        /// æ ¹æ®æ¡ç æŸ¥æ‰¾é”å®šä¿¡æ¯
        /// åˆ›å»ºå›žåº“任务
        /// </summary>
        private async Task<Dt_OutStockLockInfo> FindLockInfoByBarcode(int orderDetailId, string barcode, string materielCode)
        private void CreateReturnTask(List<Dt_Task> tasks, Dt_Task originalTask, string palletCode, Dt_LocationInfo newLocation)
        {
            return await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId &&
                           x.MaterielCode == materielCode &&
                           x.CurrentBarcode == barcode &&
                           x.Status == (int)OutLockStockStatusEnum.出库中 &&
                           x.AssignQuantity > x.PickedQty)
                .FirstAsync();
            Dt_Task newTask = new()
            {
                CurrentAddress = stations[originalTask.TargetAddress],
                Grade = 0,
                PalletCode = palletCode,
                NextAddress = "",
                OrderNo = originalTask.OrderNo,
                Roadway = newLocation.RoadwayNo,
                SourceAddress = stations[originalTask.TargetAddress],
                TargetAddress = newLocation.LocationCode,
                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                TaskType = TaskTypeEnum.InPick.ObjToInt(),
                PalletType = originalTask.PalletType,
                WarehouseId = originalTask.WarehouseId,
            };
            tasks.Add(newTask);
        }
        /// <summary>
        /// æ£€æŸ¥æ‰˜ç›˜æ˜¯å¦éœ€è¦å›žåº“的辅助方法
        /// </summary>
        public async Task<bool> CheckPalletNeedReturn(string orderNo, string palletCode)
        {
            // 1. æ£€æŸ¥æ˜¯å¦æœ‰æœªåˆ†æ‹£çš„出库记录
            var hasUnpickedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Status == 1)
                .AnyAsync();
            if (hasUnpickedLocks)
                return true;
            // 2. æ£€æŸ¥å‡ºåº“是否已完成但托盘还有库存货物
            var outboundFinished = !await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.PalletCode == palletCode && it.Status == 1)
                .AnyAsync();
            var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
            var hasRemainingGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.StockId == stockinfo.Id && it.Status == StockStatusEmun.入库确认.ObjToInt())
                .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity)
                .AnyAsync();
            return outboundFinished && hasRemainingGoods;
        }
        // å–消拣选功能
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            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)
                        .FirstAsync();
                if (outStockInfo == 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)
                       .FirstAsync();
                stockDetail.StockQuantity += outStockInfo.AssignQuantity;
                stockDetail.OutboundQuantity += outStockInfo.AssignQuantity;
                await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                // è¿˜åŽŸå‡ºåº“å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == outStockInfo.OrderDetailId)
                    .FirstAsync();
                orderDetail.OverOutQuantity -= outStockInfo.AssignQuantity;
                orderDetail.PickedQty = 0;
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                // åˆ é™¤æ‹£é€‰åŽ†å²
                await Db.Deleteable<Dt_PickingRecord>()
                    .Where(x => x.OutStockLockId == outStockInfo.Id)
                    .ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取消拣选成功");
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"取消拣选失败:{ex.Message}");
            }
        }
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
        {
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 1)
                .ToListAsync();
            return list.Where(x => x.RemainQuantity > 0).ToList();
        }
        // èŽ·å–å·²æ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode)
        {
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 6)
                .ToListAsync();
            return list;
        }
        // èŽ·å–æ‹£é€‰æ±‡æ€»
        public async Task<object> GetPickingSummary(ConfirmPickingDto dto)
        {
            var picked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
             .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
             .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
             .Where(x => x.Status == 6)
             .GroupBy(x => new { x.PalletCode, x.MaterielCode })
             .Select(x => new SummaryPickingDto
             {
                 PalletCode = x.PalletCode,
                 MaterielCode = x.MaterielCode,
                 pickedCount = SqlFunc.AggregateCount(x.Id)
             }).FirstAsync();
            if (picked == null)
            {
                picked = new SummaryPickingDto { pickedCount = 0 };
            }
            var summary = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
                .WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
                .Where(x => x.Status == 1)
                .GroupBy(x => new { x.PalletCode, x.MaterielCode })
                .Select(x => new SummaryPickingDto
                {
                    PalletCode = x.PalletCode,
                    MaterielCode = x.MaterielCode,
                    UnpickedCount = SqlFunc.AggregateCount(x.Id),
                    UnpickedQuantity = SqlFunc.AggregateSum(x.AssignQuantity) - SqlFunc.AggregateSum(x.PickedQty),
                }).FirstAsync();
            if (summary == null)
            {
                summary = new SummaryPickingDto { pickedCount = 0 };
            }
            summary.pickedCount = picked.pickedCount;
            return summary;
        }
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
        /// </summary>
@@ -173,69 +739,6 @@
                .ToListAsync();
        }
        /// <summary>
        /// æ’¤é”€æ‹£é€‰
        /// </summary>
        public async Task<WebResponseContent> CancelPicking(CancelPickingRequest request)
        {
            // å®žçŽ°æ’¤é”€æ‹£é€‰çš„é€»è¾‘ï¼Œéœ€è¦ï¼š
            // 1. æ¢å¤åº“存出库数量
            // 2. æ¢å¤é”å®šä¿¡æ¯çš„已拣选数量
            // 3. æ¢å¤å‡ºåº“单明细的已出数量和锁定数量
            // 4. åˆ é™¤æˆ–标记拣选历史记录
            // æ³¨æ„ï¼šè¿™é‡Œéœ€è¦äº‹åŠ¡å¤„ç†
            try
            {
                _unitOfWorkManage.BeginTran();
                var pickHistory = await Db.Queryable<Dt_PickingRecord>()
                    .Where(x => x.Id == request.PickingHistoryId)
                    .FirstAsync();
                if (pickHistory == null)
                    return WebResponseContent.Instance.Error("未找到拣选记录");
                // æ¢å¤åº“å­˜
                var stockDetail = await _stockInfoService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == pickHistory.Barcode && x.StockId == pickHistory.StockId)
                    .FirstAsync();
                if (stockDetail != null)
                {
                    stockDetail.OutboundQuantity -= pickHistory.PickQuantity;
                    await _stockInfoService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                }
                // æ¢å¤é”å®šä¿¡æ¯
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderDetailId == pickHistory.OrderDetailId && x.StockId == pickHistory.StockId)
                    .FirstAsync();
                lockInfo.PickedQty -= pickHistory.PickQuantity;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                // æ¢å¤å‡ºåº“单明细
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == pickHistory.OrderDetailId)
                    .FirstAsync();
                orderDetail.OverOutQuantity -= pickHistory.PickQuantity;
                orderDetail.LockQuantity += pickHistory.PickQuantity;
                if (orderDetail.OverOutQuantity < orderDetail.OrderQuantity)
                {
                    orderDetail.OrderDetailStatus = orderDetail.LockQuantity > 0 ?
                        (int)OrderDetailStatusEnum.Outbound : (int)OrderDetailStatusEnum.AssignOverPartial;
                }
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                // åˆ é™¤æ‹£é€‰åŽ†å²è®°å½•
                await Db.Deleteable<Dt_PickingRecord>().Where(x => x.Id == request.PickingHistoryId).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("撤销成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"撤销失败: {ex.Message}");
            }
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å‡ºåº“çŠ¶æ€ä¿¡æ¯
@@ -306,50 +809,59 @@
            {
                _unitOfWorkManage.BeginTran();
                // 1. èŽ·å–æ‰˜ç›˜åº“å­˜ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .Includes(x => x.Details)
                    .Where(x => x.PalletCode == request.PalletCode)
                    .FirstAsync();
                    .Where(x => x.PalletCode == request.PalletCode).FirstAsync();
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error("未找到托盘库存信息");
                // 2. èŽ·å–ç›¸å…³çš„å‡ºåº“é”å®šä¿¡æ¯
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.PalletCode == request.PalletCode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .Where(x => x.OrderNo == request.OrderNo && x.PalletCode == request.PalletCode)
                    .ToListAsync();
                // 3. æ•´ä¸ªæ‰˜ç›˜å‡ºåº“ - è®¾ç½®å‡ºåº“数量等于库存数量
                foreach (var detail in stockInfo.Details)
                {
                    decimal outboundQuantity = detail.StockQuantity - detail.OutboundQuantity;
                    detail.OutboundQuantity = detail.StockQuantity; // å…¨éƒ¨å‡ºåº“
                    await _stockInfoDetailService.Db.Updateable(detail).ExecuteCommandAsync();
                }
                // 4. æ›´æ–°å‡ºåº“锁定信息
                foreach (var lockInfo in lockInfos)
                {
                    decimal unpicked = lockInfo.AssignQuantity - lockInfo.PickedQty;
                    lockInfo.PickedQty += unpicked; // æ ‡è®°ä¸ºå…¨éƒ¨æ‹£é€‰
                    if (lockInfo.Status == (int)OutLockStockStatusEnum.出库中)
                    {
                        lockInfo.PickedQty = lockInfo.AssignQuantity;
                    }
                    lockInfo.Status = (int)OutLockStockStatusEnum.已出库;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    // æ›´æ–°å‡ºåº“单明细
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                        .Where(x => x.Id == lockInfo.OrderDetailId)
                        .FirstAsync();
                    orderDetail.OverOutQuantity += unpicked;
                    orderDetail.LockQuantity -= unpicked;
                    orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                    orderDetail.LockQuantity = 0;
                    await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    .Where(x => x.Id == lockInfo.OrderDetailId)
                    .FirstAsync();
                    if (orderDetail != null)
                    {
                        orderDetail.OverOutQuantity += lockInfo.PickedQty;
                        orderDetail.LockQuantity -= lockInfo.PickedQty;
                        orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        orderDetail.LockQuantity = 0;
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                var groupDetails = lockInfos.GroupBy(x => x.OrderDetailId).Select(x => new
                {
                    OrderDetailId = x.Key,
                    TotalQuantity = x.Sum(o => o.PickedQty)
                }).ToList();
                foreach (var item in groupDetails)
                {
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().Where(x => x.Id == item.OrderDetailId).FirstAsync();
                    if (orderDetail != null)
                    {
                        orderDetail.OverOutQuantity = item.TotalQuantity;
                        orderDetail.LockQuantity = 0;
                        orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                // 5. æ›´æ–°æ‹†åŒ…记录状态
                await CheckAndUpdateOrderStatus(request.OrderNo);
                var lockInfoIds = lockInfos.Select(x => x.Id).ToList();
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => lockInfoIds.Contains(x.OutStockLockInfoId) &&
@@ -362,7 +874,7 @@
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                // 6. æ¸…空货位
                var location = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                    .Where(x => x.LocationCode == stockInfo.LocationCode)
                    .FirstAsync();
@@ -372,6 +884,14 @@
                    await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync();
                }
                foreach (var detail in stockInfo.Details)
                {
                    await _stockInfoDetailService.Db.Deleteable(detail).ExecuteCommandAsync();
                }
                await _stockInfoService.Db.Deleteable(stockInfo).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("直接出库成功");
            }