heshaofeng
2025-12-04 1e8ca092e08494e78c398c6262aff94ac5a5bb22
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -10,6 +10,7 @@
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using WIDESEA_BasicService;
using WIDESEA_Common.CommonEnum;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.OrderEnum;
@@ -20,13 +21,20 @@
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_Core.Helper;
using WIDESEA_Core.Utilities;
using WIDESEA_DTO.Allocate;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_DTO.Outbound;
using WIDESEA_IAllocateService;
using WIDESEA_IBasicService;
using WIDESEA_ICheckService;
using WIDESEA_IInboundService;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Basic;
using WIDESEA_Model.Models.Check;
namespace WIDESEA_OutboundService
{
@@ -50,7 +58,11 @@
        private readonly IESSApiService _eSSApiService;
        private readonly IInvokeMESService _invokeMESService;
        private readonly IDailySequenceService _dailySequenceService;
        private readonly IAllocateService _allocateService;
        private readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
        private readonly IInboundOrderDetailService _inboundOrderDetailService;
        private readonly IRepository<Dt_WarehouseArea> _warehouseAreaRepository;
        private readonly IReCheckOrderService _reCheckOrderService;
        private readonly ILogger<OutboundPickingService> _logger;
        private Dictionary<string, string> stations = new Dictionary<string, string>
@@ -70,7 +82,7 @@
        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)
            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_InboundOrder> inboundOrderRepository, IInboundOrderDetailService inboundOrderDetailService, IRepository<Dt_WarehouseArea> warehouseAreaRepository, IReCheckOrderService reCheckOrderService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -86,6 +98,11 @@
            _logger = logger;
            _invokeMESService = invokeMESService;
            _dailySequenceService = dailySequenceService;
            _allocateService = allocateService;
            _inboundOrderRepository = inboundOrderRepository;
            _inboundOrderDetailService = inboundOrderDetailService;
            _warehouseAreaRepository = warehouseAreaRepository;
            _reCheckOrderService = reCheckOrderService;
        }
@@ -225,14 +242,14 @@
                }
                _unitOfWorkManage.BeginTran();
                // 1. å‰ç½®éªŒè¯
                // å‰ç½®éªŒè¯
                var validationResult = await ValidateCancelRequest(orderNo, palletCode, barcode);
                if (!validationResult.IsValid)
                    return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
                var (pickingRecord, lockInfo, orderDetail) = validationResult.Data;
                // 2. æ‰§è¡Œå–消逻辑
                //执行取消逻辑
                await ExecuteCancelLogic(lockInfo, pickingRecord, orderDetail, orderNo);
                _unitOfWorkManage.CommitTran();
@@ -291,11 +308,13 @@
                //执行回库操作
                await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, statusAnalysis);
                await ReleaseAllLocksForReallocation(orderNo, palletCode, statusAnalysis);
                _unitOfWorkManage.CommitTran();
                // åˆ›å»ºå›žåº“任务
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, TaskTypeEnum.InPick,task.PalletType);
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, TaskTypeEnum.InPick, task.PalletType);
                // æ›´æ–°è®¢å•状态(不触发MES回传)
                await UpdateOrderStatusForReturn(orderNo);
@@ -459,10 +478,21 @@
                    var completedLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(it => it.CurrentBarcode == barcode &&
                                   (it.Status == (int)OutLockStockStatusEnum.拣选完成 ||
                                    it.Status == (int)OutLockStockStatusEnum.已释放 ||
                                    it.Status == (int)OutLockStockStatusEnum.已取走 ||
                                    it.PickedQty >= it.AssignQuantity)).FirstAsync();
                    if (completedLockInfo != null)
                        throw new Exception($"条码{barcode}已经完成分拣,不能重复分拣");
                    {
                        string statusMsg = completedLockInfo.Status switch
                        {
                            (int)OutLockStockStatusEnum.拣选完成 => "已经完成分拣",
                            (int)OutLockStockStatusEnum.已释放 => "已经释放",
                            (int)OutLockStockStatusEnum.已取走 => "已经取走",
                            _ => "已经完成分拣"
                        };
                        throw new Exception($"条码{barcode}{statusMsg},不能重复分拣");
                    }
                    else
                        return null;
                }
@@ -672,9 +702,7 @@
            if (newOverOutQuantity > currentOrderDetail.NeedOutQuantity)
            {
                _logger.LogError($"防超拣检查失败 - OrderDetailId: {orderDetailId}, å·²å‡ºåº“: {newOverOutQuantity}, éœ€æ±‚: {currentOrderDetail.NeedOutQuantity}, æœ¬æ¬¡åˆ†æ‹£: {pickedQty}");
                decimal adjustedQty = currentOrderDetail.NeedOutQuantity - currentOrderDetail.OverOutQuantity;
@@ -683,6 +711,7 @@
                    _logger.LogWarning($"自动调整分拣数量防止超拣:从{pickedQty}调整为{adjustedQty}");
                    newOverOutQuantity = currentOrderDetail.NeedOutQuantity;
                    newPickedQty = currentOrderDetail.PickedQty + adjustedQty;
                    pickedQty = adjustedQty; // æ›´æ–°å®žé™…拣选数量
                }
                else
                {
@@ -690,15 +719,21 @@
                }
            }
            // æ›´æ–°è®¢å•明细
            // æ›´æ–°è®¢å•明细数量和状态
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => new Dt_OutboundOrderDetail
                {
                    PickedQty = newPickedQty,
                    OverOutQuantity = newOverOutQuantity,
                    OrderDetailStatus = newOverOutQuantity >= currentOrderDetail.NeedOutQuantity ?
                        OrderDetailStatusEnum.Over.ObjToInt() :
                        OrderDetailStatusEnum.Outbound.ObjToInt()
                })
                .Where(it => it.Id == orderDetailId)
                .ExecuteCommandAsync();
            // æ›´æ–°é”å®šæ•°é‡
            await UpdateOrderDetailLockQuantity(orderDetailId);
            // æ£€æŸ¥å¹¶æ›´æ–°è®¢å•状态
            await CheckAndUpdateOrderStatus(orderNo);
@@ -729,7 +764,15 @@
                PickQuantity = result.ActualPickedQty,
                PickTime = DateTime.Now,
                Operator = App.User.UserName,
                OutStockLockId = result.FinalLockInfo.Id
                OutStockLockId = result.FinalLockInfo.Id,
                BarcodeUnit = result.FinalLockInfo.BarcodeUnit,
                BarcodeQty = result.FinalLockInfo.BarcodeQty,
                BatchNo = result.FinalLockInfo.BatchNo,
                lineNo = result.FinalLockInfo.lineNo,
                SupplyCode = result.FinalLockInfo.SupplyCode,
                WarehouseCode = result.FinalLockInfo.WarehouseCode,
            };
            await Db.Insertable(pickingHistory).ExecuteCommandAsync();
@@ -742,7 +785,7 @@
        {
            try
            {
                // 1. éªŒè¯è®¢å•明细数据
                //  éªŒè¯è®¢å•明细数据
                var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == context.OrderDetail.Id);
@@ -752,26 +795,26 @@
                if (currentOrderDetail.PickedQty < context.PickingRecord.PickQuantity)
                    return ValidationResult<bool>.Error($"订单明细已拣选数量({currentOrderDetail.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})");
                // 2. éªŒè¯é”å®šä¿¡æ¯æ•°æ®
                //  éªŒè¯é”å®šä¿¡æ¯æ•°æ®
                var currentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .FirstAsync(x => x.Id == context.LockInfo.Id);
                if (currentLockInfo.PickedQty < context.PickingRecord.PickQuantity)
                    return ValidationResult<bool>.Error($"锁定信息已拣选数量({currentLockInfo.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})");
                // 3. éªŒè¯åº“存数据
                var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .FirstAsync(x => x.Barcode == context.PickingRecord.Barcode && x.StockId == context.PickingRecord.StockId);
                ////// éªŒè¯åº“存数据
                ////var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                ////    .FirstAsync(x => x.Barcode == context.PickingRecord.Barcode && x.StockId == context.PickingRecord.StockId);
                if (currentStockDetail == null)
                    return ValidationResult<bool>.Error($"未找到对应的库存明细记录");
                ////if (currentStockDetail == null)
                ////    return ValidationResult<bool>.Error($"未找到对应的库存明细记录");
                if (currentStockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                    currentStockDetail.Status == StockStatusEmun.入库完成.ObjToInt())
                    return ValidationResult<bool>.Error($"条码{context.PickingRecord.Barcode}已经回库,无法取消分拣");
                ////if (currentStockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                ////    currentStockDetail.Status == StockStatusEmun.入库完成.ObjToInt())
                ////    return ValidationResult<bool>.Error($"条码{context.PickingRecord.Barcode}已经回库,无法取消分拣");
                // 4. éªŒè¯çŠ¶æ€æµè½¬çš„åˆæ³•æ€§
                if (!await CanCancelPicking(currentLockInfo, currentStockDetail))
                // éªŒè¯çŠ¶æ€æµè½¬çš„åˆæ³•æ€§
                if (!await CanCancelPicking(currentLockInfo, null))
                    return ValidationResult<bool>.Error($"当前状态不允许取消分拣");
                return ValidationResult<bool>.Success(true);
@@ -785,13 +828,13 @@
        private async Task<bool> CanCancelPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail)
        {
            if (lockInfo.Status == (int)OutLockStockStatusEnum.已释放 || lockInfo.Status == (int)OutLockStockStatusEnum.已取走)
                return false;
            // é”å®šä¿¡æ¯çŠ¶æ€æ£€æŸ¥
            if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
                return false;
            // åº“存状态检查
            if (stockDetail.Status == StockStatusEmun.出库完成.ObjToInt())
                return false;
            // å¦‚果是拆包记录,还需要检查父锁定信息状态
            if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
@@ -799,7 +842,9 @@
                var parentLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .FirstAsync(x => x.Id == lockInfo.ParentLockId.Value);
                if (parentLock == null || parentLock.Status == (int)OutLockStockStatusEnum.回库中)
                if (parentLock == null || parentLock.Status == (int)OutLockStockStatusEnum.回库中 ||
                        parentLock.Status == (int)OutLockStockStatusEnum.已释放 ||
                        parentLock.Status == (int)OutLockStockStatusEnum.已取走)
                    return false;
            }
@@ -916,7 +961,7 @@
        {
            decimal cancelQty = pickingRecord.PickQuantity;
            // 1. æ•°æ®ä¸€è‡´æ€§éªŒè¯
            // æ•°æ®ä¸€è‡´æ€§éªŒè¯
            var context = new CancelPickingContext
            {
                LockInfo = lockInfo,
@@ -930,7 +975,7 @@
            if (!validationResult.IsValid)
                throw new Exception(validationResult.ErrorMessage);
            // 2. å¤„理不同类型的取消
            // å¤„理不同类型的取消
            if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
            {
                await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
@@ -956,9 +1001,9 @@
            // é‡æ–°æ£€æŸ¥è®¢å•状态
            await UpdateOrderStatusForReturn(orderNo);
        }
        private async Task HandleSplitBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
        {
            // æŸ¥æ‰¾çˆ¶é”å®šä¿¡æ¯
@@ -1092,12 +1137,28 @@
            if (newPickedQty < 0)
                throw new Exception($"取消分拣将导致已拣选数量({newPickedQty})为负数");
            // ç¡®å®šæ–°çš„状态
            int newStatus;
            if (newOverOutQuantity >= currentOrderDetail.NeedOutQuantity)
            {
                newStatus = OrderDetailStatusEnum.Over.ObjToInt();
            }
            else if (newOverOutQuantity > 0)
            {
                newStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
            }
            else
            {
                newStatus = OrderDetailStatusEnum.New.ObjToInt();
            }
            // æ›´æ–°è®¢å•明细
            var updateResult = await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => new Dt_OutboundOrderDetail
                {
                    PickedQty = newPickedQty,
                    OverOutQuantity = newOverOutQuantity
                    OverOutQuantity = newOverOutQuantity,
                    OrderDetailStatus = newStatus
                })
                .Where(it => it.Id == orderDetailId)
                .ExecuteCommandAsync();
@@ -1105,9 +1166,13 @@
            if (updateResult <= 0)
                throw new Exception("更新订单明细失败");
            // æ›´æ–°é”å®šæ•°é‡
            await UpdateOrderDetailLockQuantity(orderDetailId);
            _logger.LogInformation($"更新订单明细 - OrderDetailId: {orderDetailId}, " +
                                  $"扣减已出库: {cancelQty}, æ–°å·²å‡ºåº“: {newOverOutQuantity}, " +
                                  $"扣减已拣选: {cancelQty}, æ–°å·²æ‹£é€‰: {newPickedQty}");
                                  $"扣减已拣选: {cancelQty}, æ–°å·²æ‹£é€‰: {newPickedQty}, " +
                                  $"新状态: {newStatus}");
        }
        #endregion
@@ -1234,7 +1299,7 @@
                var targetAddress = originalTask.TargetAddress;
                await CleanupZeroStockData(stockInfoId);
                var emptystockInfo = new Dt_StockInfo() { PalletType = PalletTypeEnum.Empty.ObjToInt(), StockStatus = StockStatusEmun.组盘暂存.ObjToInt(), PalletCode = palletCode, LocationType = locationtype };
                emptystockInfo.Details = new List<Dt_StockInfoDetail>();
@@ -1242,7 +1307,7 @@
                //空托盘如何处理  è¿˜æœ‰ä¸€ä¸ªå‡ºåº“任务要处理。
                originalTask.PalletType = PalletTypeEnum.Empty.ObjToInt();
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty,PalletTypeEnum.Empty.ObjToInt());
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt());
            }
            catch (Exception ex)
@@ -1303,6 +1368,116 @@
            await UpdateStockInfoStatus(stockInfo);
        }
        /// <summary>
        /// å®Œå…¨é‡Šæ”¾é”å®šï¼Œå…è®¸é‡æ–°åˆ†é…åº“å­˜
        /// </summary>
        private async Task ReleaseAllLocksForReallocation(string orderNo, string palletCode, PalletStatusAnalysis analysis)
        {
            _logger.LogInformation($"开始释放锁定以便重新分配 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
            // 1. å¤„理未分拣的出库锁定记录 - å®Œå…¨é‡Šæ”¾
            if (analysis.HasRemainingLocks)
            {
                await ReleaseRemainingLocks(analysis.RemainingLocks);
            }
            // 2. å¤„理已回库的锁定记录 - åˆ é™¤æˆ–标记为无效
            await CleanupReturnedLocks(orderNo, palletCode);
            // 3. é‡ç½®è®¢å•明细的锁定数量
            await ResetOrderDetailLockQuantities(analysis);
            _logger.LogInformation($"锁定释放完成 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
        }
        /// <summary>
        /// é‡Šæ”¾æœªåˆ†æ‹£çš„锁定记录
        /// </summary>
        private async Task ReleaseRemainingLocks(List<Dt_OutStockLockInfo> remainingLocks)
        {
            var lockIds = remainingLocks.Select(x => x.Id).ToList();
            // å°†é”å®šè®°å½•状态改为"已释放",或者直接删除
            //  æ ‡è®°ä¸ºå·²é‡Šæ”¾
            await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                .SetColumns(it => new Dt_OutStockLockInfo
                {
                    Status = (int)OutLockStockStatusEnum.已释放,
                    Operator = App.User.UserName
                })
                .Where(it => lockIds.Contains(it.Id))
                .ExecuteCommandAsync();
            //  ç›´æŽ¥åˆ é™¤ï¼ˆæ›´å½»åº•)
            // await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
            //     .Where(it => lockIds.Contains(it.Id))
            //     .ExecuteCommandAsync();
            _logger.LogInformation($"释放{remainingLocks.Count}条未分拣锁定记录");
        }
        /// <summary>
        /// æ¸…理已回库的锁定记录
        /// </summary>
        private async Task CleanupReturnedLocks(string orderNo, string palletCode)
        {
            // æŸ¥æ‰¾æ‰€æœ‰çŠ¶æ€ä¸ºå›žåº“ä¸­çš„é”å®šè®°å½•å¹¶é‡Šæ”¾
            var returnedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(it => it.OrderNo == orderNo &&
                           it.PalletCode == palletCode &&
                           it.Status == (int)OutLockStockStatusEnum.回库中)
                .ToListAsync();
            if (returnedLocks.Any())
            {
                var returnedLockIds = returnedLocks.Select(x => x.Id).ToList();
                await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
                    .SetColumns(it => new Dt_OutStockLockInfo
                    {
                        Status = (int)OutLockStockStatusEnum.已释放,
                        //ReleaseTime = DateTime.Now,
                        Operator = App.User.UserName
                    })
                    .Where(it => returnedLockIds.Contains(it.Id))
                    .ExecuteCommandAsync();
                _logger.LogInformation($"清理{returnedLocks.Count}条回库中锁定记录");
            }
        }
        /// <summary>
        /// é‡ç½®è®¢å•明细的锁定数量
        /// </summary>
        private async Task ResetOrderDetailLockQuantities(PalletStatusAnalysis analysis)
        {
            // æ”¶é›†æ‰€æœ‰å—影响的订单明细ID
            var affectedOrderDetailIds = new HashSet<int>();
            if (analysis.HasRemainingLocks)
            {
                foreach (var lockInfo in analysis.RemainingLocks)
                {
                    affectedOrderDetailIds.Add(lockInfo.OrderDetailId);
                }
            }
            // é‡ç½®è¿™äº›è®¢å•明细的锁定数量
            foreach (var orderDetailId in affectedOrderDetailIds)
            {
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => new Dt_OutboundOrderDetail
                    {
                        LockQuantity = 0, // é‡ç½®é”å®šæ•°é‡
                        OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt() // é‡ç½®çŠ¶æ€ä¸ºæ–°å»º
                    })
                    .Where(it => it.Id == orderDetailId)
                    .ExecuteCommandAsync();
            }
            _logger.LogInformation($"重置{affectedOrderDetailIds.Count}个订单明细的锁定数量");
        }
        private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks, int stockId)
        {
            var lockIds = remainingLocks.Select(x => x.Id).ToList();
@@ -1334,6 +1509,7 @@
                if (stockDetail != null)
                {
                    stockDetail.StockQuantity += returnQty;
                    // æ¢å¤åº“存状态
                    stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
                    stockDetail.Status = StockStatusEmun.入库确认.ObjToInt();
@@ -1341,6 +1517,7 @@
                }
                else
                {
                    _logger.LogWarning($"未找到对应的库存明细 - æ¡ç : {lockInfo.CurrentBarcode}, åº“å­˜ID: {lockInfo.StockId}");
                    // åˆ›å»ºæ–°çš„库存记录
                    //var newStockDetail = new Dt_StockInfoDetail
                    //{
@@ -1360,7 +1537,34 @@
                    //};
                    //await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                }
                try
                {
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                        .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
                    if (orderDetail != null)
                    {
                        decimal newLockQuantity = Math.Max(0, orderDetail.LockQuantity - returnQty);
                        await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                            .SetColumns(it => new Dt_OutboundOrderDetail
                            {
                                LockQuantity = newLockQuantity
                            })
                            .Where(it => it.Id == lockInfo.OrderDetailId)
                            .ExecuteCommandAsync();
                        _logger.LogInformation($"更新订单明细锁定数量 - OrderDetailId: {lockInfo.OrderDetailId}, " +
                                             $"扣减锁定: {returnQty}, æ–°é”å®šæ•°é‡: {newLockQuantity}");
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError($"更新订单明细锁定数量失败 - OrderDetailId: {lockInfo.OrderDetailId}, Error: {ex.Message}");
                }
            }
        }
        private async Task UpdateOrderDetailsOnReturn(List<Dt_OutStockLockInfo> remainingLocks)
@@ -1455,7 +1659,7 @@
        /// <param name="originalTask"></param>
        /// <param name="analysis"></param>
        /// <returns></returns>
        private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, TaskTypeEnum taskTypeEnum,int palletType)
        private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, TaskTypeEnum taskTypeEnum, int palletType)
        {
            var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                .FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
@@ -1478,7 +1682,7 @@
                TaskType = taskTypeEnum.ObjToInt(),
                PalletType = palletType,
                WarehouseId = originalTask.WarehouseId
            };
            // ä¿å­˜å›žåº“任务
            await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
@@ -1486,7 +1690,7 @@
            // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
            _taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
            // await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
@@ -1512,15 +1716,15 @@
                    containerCode = palletCode
                });
                if (moveResult)
                //if (moveResult)
                //{
                // 2. åˆ›å»ºå›žåº“任务
                var essTask = new TaskModel()
                {
                    // 2. åˆ›å»ºå›žåº“任务
                    var essTask = new TaskModel()
                    {
                        taskType = "putaway",
                        taskGroupCode = "",
                        groupPriority = 0,
                        tasks = new List<TasksType>{  new() {
                    taskType = "putaway",
                    taskGroupCode = "",
                    groupPriority = 0,
                    tasks = new List<TasksType>{  new() {
                            taskCode = returnTask.TaskNum.ToString(),
                            taskPriority = 0,
                            taskDescribe = new TaskDescribeType
@@ -1534,11 +1738,11 @@
                                storageTag = ""
                            }
                        } }
                    };
                };
                    var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                    _logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                }
                var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                _logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                //}
            }
            catch (Exception ex)
            {
@@ -1562,13 +1766,25 @@
                    .ToListAsync();
                bool allCompleted = true;
                bool hasPartial = false;
                bool hasLocked = false;
                foreach (var detail in orderDetails)
                {
                    if (detail.OverOutQuantity < detail.NeedOutQuantity)
                    {
                        allCompleted = false;
                        break;
                    }
                    if (detail.OrderDetailStatus == OrderDetailStatusEnum.Outbound.ObjToInt())
                    {
                        hasPartial = true;
                    }
                    //if (detail.OrderDetailStatus == OrderDetailStatusEnum.Locked.ObjToInt())
                    //{
                    //    hasLocked = true;
                    //}
                }
                var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
@@ -1576,20 +1792,34 @@
                if (outboundOrder == null) return;
                int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
                int newStatus;
                if (allCompleted)
                {
                    newStatus = (int)OutOrderStatusEnum.出库完成;
                }
                else if (hasPartial)
                {
                    newStatus = (int)OutOrderStatusEnum.出库中;
                }
                else
                {
                    newStatus = (int)OutOrderStatusEnum.未开始;
                }
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                        .SetColumns(x => new Dt_OutboundOrder
                        {
                            OrderStatus = newStatus,
                            Operator = App.User.UserName,
                        })
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                    // åªæœ‰æ­£å¸¸åˆ†æ‹£å®Œæˆæ—¶æ‰å‘MES反馈
                    //if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
                    //{
                    //    await HandleOrderCompletion(outboundOrder, orderNo);
                    //}
                    _logger.LogInformation($"订单状态更新 - OrderNo: {orderNo}, æ—§çŠ¶æ€: {outboundOrder.OrderStatus}, æ–°çŠ¶æ€: {newStatus}");
                }
            }
            catch (Exception ex)
@@ -1598,8 +1828,70 @@
            }
        }
        /// <summary>
        /// æ›´æ–°è®¢å•明细状态(基于已出库数量)
        /// </summary>
        private async Task UpdateOrderDetailStatus(int orderDetailId)
        {
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == orderDetailId);
            if (orderDetail == null) return;
            int newStatus = orderDetail.OrderDetailStatus;
            if (orderDetail.OverOutQuantity >= orderDetail.NeedOutQuantity)
            {
                // å·²å‡ºåº“数量 >= éœ€æ±‚数量,标记为完成
                newStatus = OrderDetailStatusEnum.Over.ObjToInt();
            }
            else if (orderDetail.OverOutQuantity > 0)
            {
                // æœ‰éƒ¨åˆ†å‡ºåº“,但未完成
                newStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
            }
            else if (orderDetail.LockQuantity > 0)
            {
                // æœ‰é”å®šæ•°é‡ï¼Œä½†æœªå‡ºåº“
                //newStatus = OrderDetailStatusEnum.Locked.ObjToInt();
            }
            else
            {
                // æ–°å»ºçŠ¶æ€
                newStatus = OrderDetailStatusEnum.New.ObjToInt();
            }
            // åªæœ‰çŠ¶æ€å‘ç”Ÿå˜åŒ–æ—¶æ‰æ›´æ–°
            if (orderDetail.OrderDetailStatus != newStatus)
            {
                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                    .SetColumns(it => new Dt_OutboundOrderDetail
                    {
                        OrderDetailStatus = newStatus,
                    })
                    .Where(it => it.Id == orderDetailId)
                    .ExecuteCommandAsync();
            }
        }
        /// <summary>
        /// æ›´æ–°è®¢å•明细锁定数量
        /// </summary>
        private async Task UpdateOrderDetailLockQuantity(int orderDetailId)
        {
            var totalLockedQty = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId &&
                           x.Status == (int)OutLockStockStatusEnum.出库中)
                .SumAsync(x => x.AssignQuantity - x.PickedQty);
            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                .SetColumns(it => new Dt_OutboundOrderDetail
                {
                    LockQuantity = totalLockedQty
                })
                .Where(it => it.Id == orderDetailId)
                .ExecuteCommandAsync();
        }
        private async Task UpdateOrderStatusForReturn(string orderNo)
        {
            try
@@ -1630,7 +1922,11 @@
                if (outboundOrder.OrderStatus != newStatus)
                {
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.OrderStatus == newStatus)
                          .SetColumns(x => new Dt_OutboundOrder
                          {
                              OrderStatus = newStatus,
                              Operator = App.User.UserName,
                          })
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
@@ -1646,73 +1942,141 @@
        private async Task HandleOrderCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        {
            // è°ƒæ‹¨å‡ºåº“和重检出库不需要反馈MES
            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt() ||
                outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt())
            {
                return;
            }
            try
            {
                var feedmodel = new FeedbackOutboundRequestModel
                var allocate = _allocateService.Repository.QueryData(x => x.UpperOrderNo == outboundOrder.UpperOrderNo).First();
                var allocatefeedmodel = new AllocateDto
                {
                    reqCode = Guid.NewGuid().ToString(),
                    reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    business_type = outboundOrder.BusinessType,
                    factoryArea = outboundOrder.FactoryArea,
                    operationType = 1,
                    Operator = App.User.UserName,
                    orderNo = outboundOrder.UpperOrderNo,
                    documentsNO = outboundOrder.OrderNo,
                    status = outboundOrder.OrderStatus,
                    details = new List<FeedbackOutboundDetailsModel>()
                };
                    ReqCode = Guid.NewGuid().ToString(),
                    ReqTime = DateTime.Now.ToString(),
                    BusinessType = "2",
                    FactoryArea = outboundOrder.FactoryArea,
                    OperationType = 1,
                    Operator = App.User.UserName,
                    OrderNo = outboundOrder.UpperOrderNo,
                    // documentsNO = outboundOrder.OrderNo,
                    // status = outboundOrder.OrderStatus,
                    fromWarehouse = allocate?.FromWarehouse ?? "",
                    toWarehouse = allocate?.ToWarehouse ?? "",
                    Details = new List<AllocateDtoDetail>()
                };
                // åªèŽ·å–å·²æ‹£é€‰å®Œæˆçš„é”å®šè®°å½•
                var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成)
                    .ToListAsync();
                var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
                   .Select(group => new FeedbackOutboundDetailsModel
                var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
                   .Select(group => new AllocateDtoDetail
                   {
                       materialCode = group.Key.MaterielCode,
                       lineNo = group.Key.lineNo,
                       warehouseCode = group.Key.WarehouseCode,
                       qty = group.Sum(x => x.PickedQty),
                       currentDeliveryQty = group.Sum(x => x.PickedQty),
                       unit = group.Key.Unit,
                       barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                       MaterialCode = group.Key.MaterielCode,
                       LineNo = group.Key.lineNo,
                       WarehouseCode = group.Key.WarehouseCode,
                       Qty = group.Sum(x => x.PickedQty),
                       Unit = group.Key.BarcodeUnit,
                       Barcodes = group.Select(row => new BarcodeInfo
                       {
                           barcode = row.CurrentBarcode,
                           supplyCode = row.SupplyCode,
                           batchNo = row.BatchNo,
                           unit = row.Unit,
                           qty = row.PickedQty
                           Barcode = row.CurrentBarcode,
                           SupplyCode = row.SupplyCode,
                           BatchNo = row.BatchNo,
                           Unit = row.BarcodeUnit,
                           Qty = row.PickedQty
                       }).ToList()
                   }).ToList();
                allocatefeedmodel.Details = groupedData;
                feedmodel.details = groupedData;
                var result = await _invokeMESService.FeedbackOutbound(feedmodel);
                var result = await _invokeMESService.FeedbackAllocate(allocatefeedmodel);
                if (result != null && result.code == 200)
                {
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                        .SetColumns(x => x.ReturnToMESStatus == 1)
                        .Where(x => x.OrderId == outboundOrder.Id)
                        .ExecuteCommandAsync();
                           .SetColumns(x => x.ReturnToMESStatus == 1)
                           .Where(x => x.OrderId == outboundOrder.Id).ExecuteCommandAsync();
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.ReturnToMESStatus == 1)
                        .Where(x => x.OrderNo == orderNo)
                        .ExecuteCommandAsync();
                          .SetColumns(x => new Dt_OutboundOrder
                          {
                              ReturnToMESStatus = 1,
                              Operator = App.User.UserName,
                          }).Where(x => x.OrderNo == orderNo).ExecuteCommandAsync();
                }
                _logger.LogError($"FeedbackOutbound成功 - OrderNo: {orderNo}, {JsonSerializer.Serialize(result)}");
            }
            catch (Exception ex)
            else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
            {
                _logger.LogError($"FeedbackOutbound失败 - OrderNo: {orderNo}, Error: {ex.Message}");
            }
            else
            {
                try
                {
                    var feedmodel = new FeedbackOutboundRequestModel
                    {
                        reqCode = Guid.NewGuid().ToString(),
                        reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                        business_type = outboundOrder.BusinessType,
                        factoryArea = outboundOrder.FactoryArea,
                        operationType = 1,
                        Operator = App.User.UserName,
                        orderNo = outboundOrder.UpperOrderNo,
                        documentsNO = outboundOrder.OrderNo,
                        status = outboundOrder.OrderStatus,
                        details = new List<FeedbackOutboundDetailsModel>()
                    };
                    // åªèŽ·å–å·²æ‹£é€‰å®Œæˆçš„é”å®šè®°å½•
                    var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.OrderNo == orderNo && (x.Status == (int)OutLockStockStatusEnum.拣选完成 || x.Status == (int)OutLockStockStatusEnum.已回库))
                        .ToListAsync();
                    var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
                       .Select(group => new FeedbackOutboundDetailsModel
                       {
                           materialCode = group.Key.MaterielCode,
                           lineNo = group.Key.lineNo,
                           warehouseCode = group.Key.WarehouseCode,
                           qty = group.Sum(x => x.PickedQty),
                           currentDeliveryQty = group.Sum(x => x.PickedQty),
                           unit = group.Key.BarcodeUnit,
                           barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
                           {
                               barcode = row.CurrentBarcode,
                               supplyCode = row.SupplyCode,
                               batchNo = row.BatchNo,
                               unit = row.BarcodeUnit,
                               qty = row.PickedQty
                           }).ToList()
                       }).ToList();
                    feedmodel.details = groupedData;
                    var result = await _invokeMESService.FeedbackOutbound(feedmodel);
                    if (result != null && result.code == 200)
                    {
                        await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                            .SetColumns(x => x.ReturnToMESStatus == 1)
                            .Where(x => x.OrderId == outboundOrder.Id)
                            .ExecuteCommandAsync();
                        await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                              .SetColumns(x => new Dt_OutboundOrder
                              {
                                  ReturnToMESStatus = 1,
                                  Operator = App.User.UserName,
                              })
                            .Where(x => x.OrderNo == orderNo)
                            .ExecuteCommandAsync();
                    }
                    _logger.LogError($"FeedbackOutbound成功 - OrderNo: {orderNo}, {JsonSerializer.Serialize(result)}");
                }
                catch (Exception ex)
                {
                    _logger.LogError($"FeedbackOutbound失败 - OrderNo: {orderNo}, Error: {ex.Message}");
                }
            }
        }
@@ -1729,8 +2093,7 @@
            {
                // 1. åˆ é™¤åº“存数量为0的明细记录
                var deleteDetailCount = await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId && x.StockQuantity == 0 && (x.Status == StockStatusEmun.出库完成.ObjToInt() || x.Status ==
                                          StockStatusEmun.入库完成.ObjToInt()))
                    .Where(x => x.StockId == stockId && x.StockQuantity == 0)
                    .ExecuteCommandAsync();
                await _stockInfoService.Db.Deleteable<Dt_StockInfo>()
@@ -1845,12 +2208,13 @@
            if (outboundOrder != null && allCompleted && outboundOrder.OrderStatus != (int)OutOrderStatusEnum.出库完成)
            {
                outboundOrder.OrderStatus = (int)OutOrderStatusEnum.出库完成;
                outboundOrder.Operator = App.User.UserName;
                await _outboundOrderService.Db.Updateable(outboundOrder).ExecuteCommandAsync();
                _logger.LogInformation($"订单 {orderNo} å·²æ ‡è®°ä¸ºå‡ºåº“完成");
                // å‘MES反馈订单完成(如果需要)
                await HandleOrderCompletion(outboundOrder, orderNo);
                //await HandleOrderCompletion(outboundOrder, orderNo);
            }
        }
@@ -2087,6 +2451,8 @@
                FactoryArea = originalLock.FactoryArea,
                lineNo = originalLock.lineNo,
                WarehouseCode = originalLock.WarehouseCode,
                BarcodeQty = originalLock.BarcodeQty,
                BarcodeUnit = originalLock.BarcodeUnit,
            };
@@ -2192,8 +2558,588 @@
            return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
        }
        #region è™šæ‹Ÿå‡ºå…¥åº“
        public WebResponseContent GetAvailablePurchaseOrders()
        {
            List<Dt_InboundOrder> InOders = _inboundOrderRepository.QueryData().Where(x => x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).ToList();
            List<string> InOderCodes = InOders.Select(x => x.UpperOrderNo).ToList();
            return WebResponseContent.Instance.OK("成功", data: InOderCodes);
        }
        public WebResponseContent GetAvailablePickingOrders()
        {
            List<Dt_OutboundOrder> outOders = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().Where(x => x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).ToList();
            List<string> outOderCodes = outOders.Select(x => x.UpperOrderNo).ToList();
            return WebResponseContent.Instance.OK("成功", data: outOderCodes);
        }
        public WebResponseContent BarcodeValidate(NoStockOutModel noStockOut)
        {
            try
            {
                Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First();
                if (inboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}");
                }
                var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                if (matchedDetail == null)
                {
                    return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} ä¸­æœªæ‰¾åˆ°æ¡ç ä¸º {noStockOut.barCode} çš„æ˜Žç»†ã€‚");
                }
                matchedDetail.NoStockOutQty = 0;
                Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First();
                if (outboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.inOder}");
                }
                var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == matchedDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                if (matchedCode == null)
                {
                    return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{matchedDetail.MaterielCode} å¯¹åº”的物料。");
                }
                matchedCode.NoStockOutQty = 0;
                //剩余入库数量即虚拟出入库剩余可出数量
                decimal outQuantity = matchedDetail.OrderQuantity - matchedDetail.ReceiptQuantity;
                if (outQuantity == 0)
                {
                    return WebResponseContent.Instance.Error($"该采购单中的条码对应的可出数量为0");
                }
                if (matchedCode.OrderQuantity < outQuantity)
                {
                    return WebResponseContent.Instance.Error($"该采购单中的条码对应的可出数量超出出库单出库数量{matchedDetail.OrderQuantity - matchedCode.OrderQuantity},不满足整包出库");
                }
                //单据出库锁定数量
                matchedDetail.NoStockOutQty += outQuantity;
                matchedCode.NoStockOutQty += outQuantity;
                if ((matchedCode.LockQuantity + matchedCode.NoStockOutQty) > matchedCode.OrderQuantity)
                {
                    return WebResponseContent.Instance.Error($"出库单明细数量溢出{matchedCode.LockQuantity - matchedCode.OrderQuantity}");
                }
                matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.Inbounding.ObjToInt();
                matchedCode.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt();
                _unitOfWorkManage.BeginTran();
                _inboundOrderDetailService.UpdateData(matchedDetail);
                _outboundOrderDetailService.UpdateData(matchedCode);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        public WebResponseContent DeleteBarcode(NoStockOutModel noStockOut)
        {
            try
            {
                Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First();
                if (inboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}");
                }
                var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                if (matchedDetail == null)
                {
                    return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} ä¸­æœªæ‰¾åˆ°æ¡ç ä¸º {noStockOut.barCode} çš„æ˜Žç»†ã€‚");
                }
                matchedDetail.NoStockOutQty = 0;
                if (matchedDetail.ReceiptQuantity == 0 && matchedDetail.OverInQuantity == 0)
                {
                    matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt();
                }
                Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First();
                if (outboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.inOder}");
                }
                var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == matchedDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                if (matchedCode == null)
                {
                    return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{matchedDetail.MaterielCode} å¯¹åº”的物料。");
                }
                matchedCode.NoStockOutQty = 0;
                if (matchedCode.LockQuantity == 0 && matchedCode.OverOutQuantity == 0)
                {
                    matchedCode.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt();
                }
                _unitOfWorkManage.BeginTran();
                _inboundOrderDetailService.UpdateData(matchedDetail);
                _outboundOrderDetailService.UpdateData(matchedCode);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        public async Task<WebResponseContent> NoStockOutSubmit(NoStockOutSubmit noStockOutSubmit)
        {
            try
            {
                Dt_InboundOrder inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOutSubmit.InOderSubmit && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First();
                if (inboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到采购单:{noStockOutSubmit.InOderSubmit}");
                }
                Dt_OutboundOrder outboundOrder = _inboundOrderRepository.Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOutSubmit.OutOderSubmit && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First();
                if (outboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到出库单:{noStockOutSubmit.OutOderSubmit}");
                }
                List<Dt_InboundOrderDetail> inboundOrderDetails = new List<Dt_InboundOrderDetail>();
                List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
                foreach (var BarCode in noStockOutSubmit.BarCodeSubmit)
                {
                    var inboundOrderDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == BarCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                    if (inboundOrderDetail == null)
                    {
                        return WebResponseContent.Instance.Error($"在采购单 {noStockOutSubmit.InOderSubmit} ä¸­æœªæ‰¾åˆ°æ¡ç ä¸º {BarCode} çš„æ˜Žç»†ã€‚");
                    }
                    var outboundOrderDetail = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == inboundOrderDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
                    if (outboundOrderDetail == null)
                    {
                        return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{inboundOrderDetail.MaterielCode} å¯¹åº”的物料。");
                    }
                    inboundOrderDetail.ReceiptQuantity += inboundOrderDetail.NoStockOutQty;
                    inboundOrderDetail.OverInQuantity = inboundOrderDetail.ReceiptQuantity;
                    inboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
                    inboundOrderDetails.Add(inboundOrderDetail);
                    outboundOrderDetail.LockQuantity += outboundOrderDetail.NoStockOutQty;
                    outboundOrderDetail.OverOutQuantity = outboundOrderDetail.LockQuantity;
                    if (outboundOrderDetail.OrderQuantity == outboundOrderDetail.OverOutQuantity)
                    {
                        outboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
                    }
                    outboundOrderDetails.Add(outboundOrderDetail);
                    var newLockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = outboundOrder.UpperOrderNo,
                        OrderDetailId = outboundOrderDetail.Id,
                        OutboundBatchNo = outboundOrderDetail.BatchNo,
                        MaterielCode = outboundOrderDetail.MaterielCode,
                        MaterielName = outboundOrderDetail.MaterielName,
                        StockId = 0,
                        OrderQuantity = outboundOrderDetail.OrderQuantity,
                        AssignQuantity = outboundOrderDetail.OverOutQuantity,
                        PickedQty = outboundOrderDetail.NoStockOutQty,
                        LocationCode = "空",
                        PalletCode = "空",
                        TaskNum = 0,
                        Status = (int)OutLockStockStatusEnum.拣选完成,
                        Unit = outboundOrderDetail.Unit,
                        SupplyCode = outboundOrderDetail.SupplyCode?? "无",
                        OrderType = outboundOrder.OrderType,
                        CurrentBarcode = inboundOrderDetail.Barcode,
                        IsSplitted = 1,
                        Operator = App.User.UserName,
                        lineNo= outboundOrderDetail.lineNo,
                        WarehouseCode = outboundOrderDetail.WarehouseCode ?? "无",
                        BarcodeQty=outboundOrderDetail.NoStockOutQty,
                        BarcodeUnit =outboundOrderDetail.BarcodeUnit,
                        BatchNo = outboundOrderDetail.BatchNo
                    };
                    _outStockLockInfoService.AddData(newLockInfo);
                }
                //判断入库单据明细是否全部是完成状态
                int e = inboundOrder.Details.Count();
                int w = inboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count();
                bool inoderOver = inboundOrder.Details.Count() == inboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count();
                if (inoderOver)
                {
                    inboundOrder.OrderStatus = InOrderStatusEnum.入库完成.ObjToInt();
                }
                else
                {
                    inboundOrder.OrderStatus = InOrderStatusEnum.入库中.ObjToInt();
                }
                //判断出库单据明细是否全部是完成状态
                bool outOderOver = outboundOrder.Details.Count() == outboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count();
                if (outOderOver)
                {
                    outboundOrder.OrderStatus = OutOrderStatusEnum.出库完成.ObjToInt();
                }
                else
                {
                    outboundOrder.OrderStatus = OutOrderStatusEnum.出库中.ObjToInt();
                }
                //数据处理
                _unitOfWorkManage.BeginTran();
                _inboundOrderDetailService.UpdateData(inboundOrderDetails);
                _outboundOrderDetailService.UpdateData(outboundOrderDetails);
                _inboundOrderRepository.UpdateData(inboundOrder);
                _outboundOrderService.UpdateData(outboundOrder);
                _unitOfWorkManage.CommitTran();
                //入库回传MES
                var infeedmodel = new FeedbackInboundRequestModel
                {
                    reqCode = Guid.NewGuid().ToString(),
                    reqTime = DateTime.Now.ToString(),
                    business_type = inboundOrder.BusinessType,
                    factoryArea = inboundOrder.FactoryArea,
                    operationType = 1,
                    Operator = App.User.UserName,
                    orderNo = inboundOrder.UpperOrderNo,
                    status = inboundOrder.OrderStatus,
                    details = new List<FeedbackInboundDetailsModel>()
                };
                var groupedData = inboundOrderDetails.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
                    .Select(group => new FeedbackInboundDetailsModel
                    {
                        materialCode = group.Key.MaterielCode,
                        supplyCode = group.Key.SupplyCode,
                        batchNo = group.Key.BatchNo,
                        lineNo = group.Key.lineNo,
                        warehouseCode = group.Key.WarehouseCode,
                        qty = group.Sum(x => x.BarcodeQty),
                        // warehouseCode= "1072",
                        unit = group.Key.BarcodeUnit,
                        barcodes = group.Select(row => new FeedbackBarcodesModel
                        {
                            barcode = row.Barcode,
                            qty = row.BarcodeQty
                        }).ToList()
                    }).ToList();
                infeedmodel.details = groupedData;
                var result1 = await _invokeMESService.FeedbackInbound(infeedmodel);
                if (result1 != null && result1.code == 200)
                {
                    _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1 })
                    .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                    _inboundOrderDetailService.Db.Updateable<Dt_InboundOrderDetail>().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 })
                    .Where(it => it.OrderId == inboundOrder.Id).ExecuteCommand();
                }
                //出库回传MES
                var documentNo = UniqueValueGenerator.Generate();
                var outfeedmodel = new FeedbackOutboundRequestModel
                    {
                        reqCode = Guid.NewGuid().ToString(),
                        reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                        business_type = outboundOrder.BusinessType,
                        factoryArea = outboundOrder.FactoryArea,
                        operationType = 1,
                        Operator = App.User.UserName,
                        orderNo = outboundOrder.UpperOrderNo,
                        documentsNO = documentNo,
                        status = outboundOrder.OrderStatus,
                        details = new List<FeedbackOutboundDetailsModel>()
                    };
                foreach (var detail in outboundOrder.Details)
                {
                    // èŽ·å–è¯¥æ˜Žç»†å¯¹åº”çš„æ¡ç ä¿¡æ¯ï¼ˆä»Žé”å®šè®°å½•ï¼‰
                    var detailLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.OrderNo == outboundOrder.UpperOrderNo &&
                                    x.OrderDetailId == detail.Id &&
                                        (x.Status == (int)OutLockStockStatusEnum.拣选完成 || x.Status == (int)OutLockStockStatusEnum.已回库))
                        .ToListAsync();
                        var groupdata = detailLocks.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
                              .Select(group => new FeedbackOutboundDetailsModel
                              {
                                  materialCode = group.Key.MaterielCode,
                                  lineNo = group.Key.lineNo,
                                  warehouseCode = group.Key.WarehouseCode,
                                  qty = group.Sum(x => x.PickedQty),
                                  currentDeliveryQty = group.Sum(x => x.PickedQty),
                                  unit = group.Key.BarcodeUnit,
                                  barcodes = group.Select(lockInfo => new WIDESEA_DTO.Outbound.BarcodesModel
                                  {
                                      barcode = lockInfo.CurrentBarcode,
                                      supplyCode = lockInfo.SupplyCode,
                                      batchNo = lockInfo.BatchNo,
                                      unit = lockInfo.BarcodeUnit,
                                      qty = lockInfo.PickedQty
                                  }).ToList()
                              }).ToList();
                        outfeedmodel.details.AddRange(groupdata);
                    }
                    var result = await _invokeMESService.FeedbackOutbound(outfeedmodel);
                    if (result != null && result.code == 200)
                    {
                        await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                            .SetColumns(x => x.ReturnToMESStatus == 1)
                            .Where(x => x.OrderId == outboundOrder.Id)
                            .ExecuteCommandAsync();
                    await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                        .SetColumns(x => x.ReturnToMESStatus == 1)
                        .Where(x => x.Id == outboundOrder.Id)
                        .ExecuteCommandAsync();
                }
                return WebResponseContent.Instance.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        #endregion
        public WebResponseContent UnPalletQuantity(string orderNo)
        {
            // åˆå§‹åŒ–返回DTO(默认值都为0,避免null)
            var resultDTO = new PalletSumQuantityDTO
            {
                StockSumQuantity = 0,
                StockCount = 0,
                UniqueUnit = ""
            };
            WebResponseContent content = new WebResponseContent();
            try
            {
                if (string.IsNullOrWhiteSpace(orderNo))
                {
                    return content.Error("传入的订单号orderNo为空或空白");
                }
                var orderDetail = Db.Queryable<Dt_PickingRecord>().Where(s => s.OrderNo == orderNo).ToList();
                if (orderDetail == null)
                {
                    return content.Error("未找到单据");
                }
                var unitGroups = orderDetail.GroupBy(d => d.BarcodeUnit).ToList();
                if (unitGroups.Count == 1)
                {
                    resultDTO.UniqueUnit = unitGroups.First().Key;
                }
                else
                {
                    resultDTO.UniqueUnit = "";
                }
                var validDetails = _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>().Where(s => s.OrderNo == orderNo).ToList();
                resultDTO.StockSumQuantity = orderDetail.Sum(d => d.PickQuantity);
                resultDTO.StockCount = orderDetail.Count;
                if (validDetails.Any())
                {
                    resultDTO.StockSumQuantity -= validDetails.Sum(d => d.StockQuantity);
                    // æ˜Žç»†è®°å½•数:符合条件的有效记录条数
                    resultDTO.StockCount -= validDetails.Count;
                }
                return content.OK("", resultDTO);
            }
            catch (Exception ex)
            {
                return content.Error("SumQuantity ç»Ÿè®¡åº“存数量失败,订单号:{OrderNo}");
            }
        }
        public WebResponseContent BarcodeMaterielGroup(BarcodeMaterielGroupDTO materielGroupDTO)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                (bool, string, object?) result2 = ModelValidate.ValidateModelData(materielGroupDTO);
                if (!result2.Item1) return content = WebResponseContent.Instance.Error(result2.Item2);
                //  materielGroupDTO.WarehouseCode
                var code = _warehouseAreaRepository.Db.Queryable<Dt_WarehouseArea>().Where(x => x.Code == materielGroupDTO.WarehouseType).Select(x => x.Code).First();
                if (string.IsNullOrEmpty(code))
                {
                    return content = WebResponseContent.Instance.Error($"仓库中没有该{materielGroupDTO.WarehouseType}编号。");
                }
                if (materielGroupDTO.orderTypes == InOrderTypeEnum.ReCheck.ObjToInt())
                {
                    var dborder = _reCheckOrderService.Db.Queryable<Dt_ReCheckOrder>().First(x => x.OrderNo == materielGroupDTO.OrderNo);
                    if (dborder != null && dborder.SignSeq == 0)
                    {
                        return content.Error("只有拿到重检结果才能入库!");
                    }
                }
                //  Dt_InboundOrder inboundOrder = GetInboundOrder(materielGroupDTO.OrderNo);
                var dbinboundOrderDetails = Db.Queryable<Dt_PickingRecord>().Where(x => x.OrderNo == materielGroupDTO.OrderNo && !x.IsCancelled && x.Barcode == materielGroupDTO.Barcodes).ToList();
                if (dbinboundOrderDetails != null && !dbinboundOrderDetails.Any())
                {
                    return content = WebResponseContent.Instance.Error($"单据中没有该{materielGroupDTO.Barcodes}条码数据。");
                }
                List<string?> materielCodes = dbinboundOrderDetails.GroupBy(x => x.Barcode).Select(x => x.Key).ToList();
                Dt_StockInfo? stockInfo = _stockService.StockInfoService.GetStockByPalletCode(materielGroupDTO.PalletCode);
                (bool, string, object?) result = CheckMaterielGroupParam(materielGroupDTO, materielCodes, stockInfo);
                if (!result.Item1) return content = WebResponseContent.Instance.Error(result.Item2);
                if (stockInfo == null)
                {
                    stockInfo = new Dt_StockInfo() { PalletType = (int)PalletTypeEnum.None, LocationType = materielGroupDTO.locationType.ObjToInt() };
                    stockInfo.Details = new List<Dt_StockInfoDetail>();
                }
                var inboindId = 0; Dt_InboundOrder dt_InboundOrder = null;
                var dbinbound = _inboundOrderRepository.QueryData(x => x.InboundOrderNo == dbinboundOrderDetails.First().OrderNo).First();
                if (dbinbound == null)
                {
                    dt_InboundOrder = new Dt_InboundOrder
                    {
                        WarehouseId = 0,
                        InboundOrderNo = dbinboundOrderDetails.First()?.OrderNo,
                        UpperOrderNo = dbinboundOrderDetails.First()?.OrderNo,
                        SupplierId = dbinboundOrderDetails.First()?.SupplyCode,
                        OrderType = materielGroupDTO.orderTypes,
                        BusinessType = materielGroupDTO.orderTypes.ToString(),
                        FactoryArea = dbinboundOrderDetails.First()?.FactoryArea,
                        Remark = "",
                        Details = new List<Dt_InboundOrderDetail>()
                    };
                    inboindId = _inboundOrderRepository.AddData(dt_InboundOrder);
                }
                else
                {
                    dt_InboundOrder = new Dt_InboundOrder { Details = new List<Dt_InboundOrderDetail>() };
                    inboindId = dbinbound.Id;
                }
                foreach (var item in dbinboundOrderDetails)
                {
                    stockInfo.Details.Add(new Dt_StockInfoDetail
                    {
                        StockId = stockInfo == null ? 0 : stockInfo.Id,
                        Barcode = item.Barcode,
                        MaterielCode = item.MaterielCode,
                        BatchNo = item.BatchNo,
                        Unit = item.BarcodeUnit,
                        InboundOrderRowNo = item.lineNo,
                        SupplyCode = item.SupplyCode,
                        WarehouseCode = materielGroupDTO.WarehouseType,
                        StockQuantity = item.PickQuantity,
                        BarcodeQty = item.BarcodeQty,
                        BarcodeUnit = item.BarcodeUnit,
                        FactoryArea = item.FactoryArea,
                        Status = 0,
                        OrderNo = item.OrderNo,
                        BusinessType = materielGroupDTO.orderTypes.ToString()
                    });
                    item.WarehouseCode = item.WarehouseCode;
                    dt_InboundOrder.Details.Add(new Dt_InboundOrderDetail
                    {
                        OrderId = inboindId,
                        MaterielCode = item.MaterielCode,
                        MaterielName = "",
                        BatchNo = item.BatchNo,
                        OrderQuantity = item.PickQuantity,
                        ReceiptQuantity = 0,
                        OverInQuantity = 0,
                        Unit = item.BarcodeUnit,
                        RowNo = 0,
                        lineNo = item.lineNo,
                        SupplyCode = item.SupplyCode,
                        WarehouseCode = item.WarehouseCode,
                        Barcode = item.Barcode,
                        OutBoxbarcodes = "",
                        BarcodeQty = (decimal)item.BarcodeQty,
                        BarcodeUnit = item.BarcodeUnit
                    });
                }
                _inboundOrderDetailService.Db.Insertable<Dt_InboundOrderDetail>(dt_InboundOrder.Details);
                if (stockInfo.Id == 0)
                {
                    stockInfo.PalletCode = materielGroupDTO.PalletCode;
                    stockInfo.StockStatus = StockStatusEmun.组盘暂存.ObjToInt();
                }
                stockInfo.PalletType = (int)PalletTypeEnum.None;
                List<int> updateDetailIds = dbinboundOrderDetails.Select(x => x.Id).ToList();
                try
                {
                    _unitOfWorkManage.BeginTran();
                    _stockService.StockInfoService.AddMaterielGroup(stockInfo);
                    _unitOfWorkManage.CommitTran();
                    return WebResponseContent.Instance.OK();
                }
                catch (Exception ex)
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error(ex.Message);
                }
            }
            catch (Exception ex)
            {
                content = WebResponseContent.Instance.Error(ex.Message);
            }
            finally
            {
            }
            return content;
        }
        public (bool, string, object?) CheckMaterielGroupParam(BarcodeMaterielGroupDTO materielGroupDTO, List<string> barcodeCodes, Dt_StockInfo stockInfo)
        {
            (bool, string, object?) result = ModelValidate.ValidateModelData(materielGroupDTO);
            if (!result.Item1) return result;
            if (_taskRepository.QueryFirst(x => x.PalletCode == materielGroupDTO.PalletCode) != null)
            {
                return (false, "该托盘号已有任务", materielGroupDTO);
            }
            if (stockInfo != null && !string.IsNullOrEmpty(stockInfo.LocationCode) && stockInfo.StockStatus != StockStatusEmun.组盘暂存.ObjToInt())
            {
                return (false, "已上架的托盘不能再次组盘", materielGroupDTO);
            }
            if (_stockService.StockInfoDetailService.ExistBarcodes(barcodeCodes))
            {
                return (false, $"{barcodeCodes[0]} æ¡ç åœ¨åº“存中已存在", materielGroupDTO);
            }
            return (true, "成功", materielGroupDTO);
        }
        #endregion
    }
    #region æ”¯æŒç±»å®šä¹‰
@@ -2258,12 +3204,20 @@
        public bool HasSplitRecords { get; set; }
        public decimal RemainingLocksReturnQty { get; set; }
        public decimal PalletStockReturnQty { get; set; }
        public decimal SplitReturnQty { get; set; }
        public decimal TotalReturnQty { get; set; }
        // æœªåˆ†é…çš„锁定记录(如自动拆包产生的)
        public bool HasUnallocatedLocks { get; set; }
        public List<Dt_OutStockLockInfo> UnallocatedLocks { get; set; } = new List<Dt_OutStockLockInfo>();
        public decimal UnallocatedLocksReturnQty { get; set; }
        public List<Dt_OutStockLockInfo> RemainingLocks { get; set; } = new List<Dt_OutStockLockInfo>();
        public List<Dt_StockInfoDetail> PalletStockGoods { get; set; } = new List<Dt_StockInfoDetail>();
        public List<Dt_SplitPackageRecord> SplitRecords { get; set; } = new List<Dt_SplitPackageRecord>();
        public List<string> AllBarcodes { get; set; } = new List<string>();
        // ç©ºæ‰˜ç›˜ç›¸å…³å±žæ€§
        public bool IsEmptyPallet { get; set; }
        public bool HasActiveTasks { get; set; }