647556386
3 天以前 3460ffdc967b65ea67c959212c2505a19008401e
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -1,17 +1,11 @@
using Dm.filter;
using MailKit.Search;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MailKit.Search;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
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;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
@@ -20,15 +14,30 @@
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Allocate;
using WIDESEA_Core.Utilities;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_DTO.Outbound;
using WIDESEA_DTO.ReturnMES;
using WIDESEA_IAllocateService;
using WIDESEA_IBasicService;
using WIDESEA_ICheckService;
using WIDESEA_IInboundService;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_ITaskInfoService;
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Basic;
using WIDESEA_Model.Models.Check;
using WIDESEA_Model.Models.Outbound;
using Org.BouncyCastle.Asn1.Ocsp;
using WIDESEA_BasicService.MESOperation;
using WIDESEA_Core.Util;
using WIDESEA_DTO.Allocate;
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
using WIDESEA_IRecordService;
using Microsoft.AspNetCore.Http;
using MailKit.Net.Smtp;
namespace WIDESEA_OutboundService
{
@@ -37,6 +46,7 @@
    /// </summary>
    public class OutboundPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>, IOutboundPickingService
    {
        #region æž„造函数
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        public IRepository<Dt_PickingRecord> Repository => BaseDal;
@@ -53,8 +63,19 @@
        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 ITask_HtyService _task_HtyService;
        private readonly ILogger<OutboundPickingService> _logger;
        private readonly IRepository<Dt_InterfaceLog> _interfaceLog;
        private readonly IInboundService _inboundService;
        private readonly IFeedbackMesService _feedbackMesService;
        private readonly HttpClientHelper _httpClientHelper;
        private readonly IRepository<Dt_MesReturnRecord> _mesReturnRecord;
        private readonly IStockQuantityChangeRecordService _stockQuantityChangeRecordService;
        private readonly IInboundOrderService _inboundOrderService;
        private Dictionary<string, string> stations = new Dictionary<string, string>
        {
@@ -73,7 +94,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, IAllocateService allocateService) : 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, ITask_HtyService task_HtyService, IRepository<Dt_InterfaceLog> interfaceLog, IInboundService inboundService, IFeedbackMesService feedbackMesService, HttpClientHelper httpClientHelper, IRepository<Dt_MesReturnRecord> mesReturnRecord,IStockQuantityChangeRecordService stockQuantityChangeRecordService,IInboundOrderService inboundOrderService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -90,8 +111,22 @@
            _invokeMESService = invokeMESService;
            _dailySequenceService = dailySequenceService;
            _allocateService = allocateService;
            _inboundOrderRepository = inboundOrderRepository;
            _inboundOrderDetailService = inboundOrderDetailService;
            _warehouseAreaRepository = warehouseAreaRepository;
            _reCheckOrderService = reCheckOrderService;
            _task_HtyService = task_HtyService;
            _interfaceLog = interfaceLog;
            _inboundService = inboundService;
            _feedbackMesService = feedbackMesService;
            _httpClientHelper = httpClientHelper;
            _mesReturnRecord = mesReturnRecord;
            _stockQuantityChangeRecordService = stockQuantityChangeRecordService;
            _inboundOrderService = inboundOrderService;
        }
        #endregion
        #region æŸ¥è¯¢æ–¹æ³•
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
@@ -278,15 +313,31 @@
                    return WebResponseContent.Instance.Error("未找到对应的任务信息");
                //分析需要回库的货物
                //var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id);
                //if (!returnAnalysis.HasItemsToReturn)
                //    return await HandleNoReturnItems(orderNo, palletCode, task);
                var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id);
                if (!statusAnalysis.HasItemsToReturn)
                    return await HandleNoReturnItems(orderNo, palletCode, task, stockInfo.Id);
                {
                    try
                    {
                        var result = await HandleNoReturnItems(orderNo, palletCode, task, stockInfo.Id);
                        _unitOfWorkManage.CommitTran();
                        if (result.Status)
                        {
                            task.PalletType = PalletTypeEnum.Empty.ObjToInt();
                            await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt());
                        }
                        return result;
                    }
                    catch (Exception ex)
                    {
                        _unitOfWorkManage.RollbackTran();
                        _logger.LogError($"ReturnRemaining å›žåº“空箱失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                        return WebResponseContent.Instance.Error($"回库空箱操作失败: {ex.Message}");
                    }
                // 4. æ£€æŸ¥æ˜¯å¦æœ‰è¿›è¡Œä¸­çš„任务
                }
                // æ£€æŸ¥æ˜¯å¦æœ‰è¿›è¡Œä¸­çš„任务
                if (statusAnalysis.HasActiveTasks)
                {
                    return WebResponseContent.Instance.Error($"托盘 {palletCode} æœ‰è¿›è¡Œä¸­çš„任务,不能执行回库操作");
@@ -294,6 +345,8 @@
                //执行回库操作
                await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, statusAnalysis);
                await ReleaseAllLocksForReallocation(orderNo, palletCode, statusAnalysis);
                _unitOfWorkManage.CommitTran();
@@ -386,16 +439,16 @@
        private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>> ValidatePickingRequest(string orderNo, string palletCode, string barcode)
        {
            // 1. åŸºç¡€å‚数验证
            // åŸºç¡€å‚数验证
            if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("订单号、托盘码和条码不能为空");
            // 2. æŸ¥æ‰¾æœ‰æ•ˆçš„锁定信息
            // æŸ¥æ‰¾æœ‰æ•ˆçš„锁定信息
            var lockInfo = await FindValidLockInfo(orderNo, palletCode, barcode);
            if (lockInfo == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到有效的锁定信息");
            // 3. æ£€æŸ¥è®¢å•状态
            // æ£€æŸ¥è®¢å•状态
            var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == orderNo)
                .FirstAsync();
@@ -403,18 +456,18 @@
            if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单{orderNo}已完成,不能继续分拣");
            // 4. èŽ·å–è®¢å•æ˜Žç»†
            // èŽ·å–è®¢å•æ˜Žç»†
            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
            if (orderDetail == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到订单明细");
            // 5. æ£€æŸ¥è®¢å•明细数量
            // æ£€æŸ¥è®¢å•明细数量
            if (orderDetail.OverOutQuantity >= orderDetail.NeedOutQuantity)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单明细需求数量已满足");
            // 6. èŽ·å–åº“å­˜æ˜Žç»†
            // èŽ·å–åº“å­˜æ˜Žç»†
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId &&
                   x.Status != StockStatusEmun.入库确认.ObjToInt())
@@ -423,14 +476,14 @@
            if (stockDetail == null)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"无效的条码或物料编码");
            // 7. æ£€æŸ¥åº“存状态和数量
            // æ£€æŸ¥åº“存状态和数量
            if (stockDetail.StockQuantity <= 0)
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}库存不足");
            if (stockDetail.Status != StockStatusEmun.出库锁定.ObjToInt())
                return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}状态不正确,无法分拣");
            // 8. æ£€æŸ¥æ˜¯å¦é‡å¤åˆ†æ‹£
            //检查是否重复分拣
            var existingPicking = await Db.Queryable<Dt_PickingRecord>()
                .Where(x => x.Barcode == barcode && x.OrderNo == orderNo && x.PalletCode == palletCode && x.OutStockLockId == lockInfo.Id)
                .FirstAsync();
@@ -463,10 +516,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;
                }
@@ -628,13 +692,13 @@
        private async Task HandleFullPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
            decimal actualQty, PickingResult result)
        {
            // 1. æ›´æ–°åº“å­˜
            //  æ›´æ–°åº“å­˜
            stockDetail.StockQuantity = 0;
            stockDetail.OutboundQuantity = 0;
            stockDetail.Status = StockStatusEmun.出库完成.ObjToInt();
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. æ›´æ–°é”å®šä¿¡æ¯
            // æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += actualQty;
            lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
            lockInfo.Operator = App.User.UserName;
@@ -647,19 +711,19 @@
            decimal stockOutQty = stockQuantity;
            decimal remainingAssignQty = actualQty - stockQuantity;
            // 1. æ›´æ–°åº“å­˜
            // æ›´æ–°åº“å­˜
            stockDetail.StockQuantity = 0;
            stockDetail.OutboundQuantity = 0;
            stockDetail.Status = StockStatusEmun.出库完成.ObjToInt();
            await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
            // 2. æ›´æ–°é”å®šä¿¡æ¯
            // æ›´æ–°é”å®šä¿¡æ¯
            lockInfo.PickedQty += stockOutQty;
            lockInfo.AssignQuantity = remainingAssignQty;
            lockInfo.Operator = App.User.UserName;
            await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
            // 3. æ›´æ–°æ‹†åŒ…记录状态
            // æ›´æ–°æ‹†åŒ…记录状态
            await UpdateSplitRecordsStatus(stockDetail.Barcode);
            result.ActualPickedQty = stockOutQty;
@@ -676,9 +740,7 @@
            if (newOverOutQuantity > currentOrderDetail.NeedOutQuantity)
            {
                _logger.LogError($"防超拣检查失败 - OrderDetailId: {orderDetailId}, å·²å‡ºåº“: {newOverOutQuantity}, éœ€æ±‚: {currentOrderDetail.NeedOutQuantity}, æœ¬æ¬¡åˆ†æ‹£: {pickedQty}");
                decimal adjustedQty = currentOrderDetail.NeedOutQuantity - currentOrderDetail.OverOutQuantity;
@@ -687,6 +749,7 @@
                    _logger.LogWarning($"自动调整分拣数量防止超拣:从{pickedQty}调整为{adjustedQty}");
                    newOverOutQuantity = currentOrderDetail.NeedOutQuantity;
                    newPickedQty = currentOrderDetail.PickedQty + adjustedQty;
                    pickedQty = adjustedQty; // æ›´æ–°å®žé™…拣选数量
                }
                else
                {
@@ -694,15 +757,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);
@@ -733,7 +802,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();
@@ -763,19 +840,19 @@
                if (currentLockInfo.PickedQty < context.PickingRecord.PickQuantity)
                    return ValidationResult<bool>.Error($"锁定信息已拣选数量({currentLockInfo.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})");
                // éªŒè¯åº“存数据
                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}已经回库,无法取消分拣");
                // éªŒè¯çŠ¶æ€æµè½¬çš„åˆæ³•æ€§
                if (!await CanCancelPicking(currentLockInfo, currentStockDetail))
                if (!await CanCancelPicking(currentLockInfo, null))
                    return ValidationResult<bool>.Error($"当前状态不允许取消分拣");
                return ValidationResult<bool>.Success(true);
@@ -789,13 +866,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)
@@ -803,7 +880,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;
            }
@@ -883,22 +962,7 @@
            return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Success((pickingRecord, lockInfo, orderDetail));
        }
        /// <summary>
        /// æ£€æŸ¥æ¡ç æ˜¯å¦å·²ç»å›žåº“
        /// </summary>
        private async Task<bool> IsBarcodeReturned(string barcode, int stockId)
        {
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(it => it.Barcode == barcode && it.StockId == stockId)
                .FirstAsync();
            if (stockDetail == null)
                return false;
            // å¦‚果状态是入库确认或入库完成,说明已经回库
            return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
                   stockDetail.Status == StockStatusEmun.入库完成.ObjToInt();
        }
        /// <summary>
        /// æ£€æŸ¥é”å®šä¿¡æ¯å¯¹åº”的条码是否已经回库
@@ -1083,7 +1147,7 @@
        {
            // èŽ·å–æœ€æ–°çš„è®¢å•æ˜Žç»†æ•°æ®ï¼ˆå¸¦é”ï¼‰
            var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .With(SqlWith.RowLock)
                //.With(SqlWith.RowLock)
                .FirstAsync(x => x.Id == orderDetailId);
            decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity - cancelQty;
@@ -1096,12 +1160,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();
@@ -1109,19 +1189,18 @@
            if (updateResult <= 0)
                throw new Exception("更新订单明细失败");
            // æ›´æ–°é”å®šæ•°é‡
            await UpdateOrderDetailLockQuantity(orderDetailId);
            _logger.LogInformation($"更新订单明细 - OrderDetailId: {orderDetailId}, " +
                                  $"扣减已出库: {cancelQty}, æ–°å·²å‡ºåº“: {newOverOutQuantity}, " +
                                  $"扣减已拣选: {cancelQty}, æ–°å·²æ‹£é€‰: {newPickedQty}");
                                  $"扣减已拣选: {cancelQty}, æ–°å·²æ‹£é€‰: {newPickedQty}, " +
                                  $"新状态: {newStatus}");
        }
        #endregion
        #region å›žåº“操作私有方法
        private async Task<Dt_StockInfo> GetStockInfo(string palletCode)
        {
            return await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .FirstAsync(x => x.PalletCode == palletCode);
        }
        /// <summary>
        /// æ£€æŸ¥æ•´ä¸ªæ‰˜ç›˜æ˜¯å¦å·²ç»å›žåº“
        /// </summary>
@@ -1200,23 +1279,6 @@
        private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode, Dt_Task originalTask, int stockInfoId)
        {
            // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰è´§ç‰©éƒ½å·²æ‹£é€‰å®Œæˆ
            //var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
            //    .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
            //    .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
            //if (allPicked)
            //{
            //    // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡ ç»„空盘   ç©ºç›˜å›žåº“
            //    //await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            //    return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
            //}
            //else
            //{
            //    // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
            //    //await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            //    return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
            //}
            try
            {
                var locationtype = 0;
@@ -1245,8 +1307,6 @@
                _stockInfoService.AddMaterielGroup(emptystockInfo);
                //空托盘如何处理  è¿˜æœ‰ä¸€ä¸ªå‡ºåº“任务要处理。
                originalTask.PalletType = PalletTypeEnum.Empty.ObjToInt();
                await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt());
            }
            catch (Exception ex)
@@ -1307,6 +1367,116 @@
            await UpdateStockInfoStatus(stockInfo);
        }
        /// <summary>
        /// å®Œå…¨é‡Šæ”¾é”å®šï¼Œå…è®¸é‡æ–°åˆ†é…åº“å­˜
        /// </summary>
        private async Task ReleaseAllLocksForReallocation(string orderNo, string palletCode, PalletStatusAnalysis analysis)
        {
            _logger.LogInformation($"开始释放锁定以便重新分配 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
            // å¤„理未分拣的出库锁定记录 - å®Œå…¨é‡Šæ”¾
            if (analysis.HasRemainingLocks)
            {
                await ReleaseRemainingLocks(analysis.RemainingLocks);
            }
            // å¤„理已回库的锁定记录 - åˆ é™¤æˆ–标记为无效
            await CleanupReturnedLocks(orderNo, palletCode);
            // é‡ç½®è®¢å•明细的锁定数量
            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();
@@ -1338,6 +1508,7 @@
                if (stockDetail != null)
                {
                    stockDetail.StockQuantity += returnQty;
                    // æ¢å¤åº“存状态
                    stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
                    stockDetail.Status = StockStatusEmun.入库确认.ObjToInt();
@@ -1345,6 +1516,7 @@
                }
                else
                {
                    _logger.LogWarning($"未找到对应的库存明细 - æ¡ç : {lockInfo.CurrentBarcode}, åº“å­˜ID: {lockInfo.StockId}");
                    // åˆ›å»ºæ–°çš„库存记录
                    //var newStockDetail = new Dt_StockInfoDetail
                    //{
@@ -1364,40 +1536,36 @@
                    //};
                    //await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                }
            }
        }
        private async Task UpdateOrderDetailsOnReturn(List<Dt_OutStockLockInfo> remainingLocks)
        {
            // æŒ‰è®¢å•明细分组
            var orderDetailGroups = remainingLocks.GroupBy(x => x.OrderDetailId);
            foreach (var group in orderDetailGroups)
            {
                var orderDetailId = group.Key;
                var totalReturnQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
                // èŽ·å–å½“å‰è®¢å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .FirstAsync(x => x.Id == orderDetailId);
                if (orderDetail != null)
                try
                {
                    // è°ƒæ•´å·²æ‹£é€‰æ•°é‡å’Œå·²å‡ºåº“数量
                    decimal newPickedQty = Math.Max(0, orderDetail.PickedQty - totalReturnQty);
                    decimal newOverOutQuantity = Math.Max(0, orderDetail.OverOutQuantity - totalReturnQty);
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                        .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
                    await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                        .SetColumns(it => new Dt_OutboundOrderDetail
                        {
                            PickedQty = newPickedQty,
                            OverOutQuantity = newOverOutQuantity,
                        })
                        .Where(it => it.Id == orderDetailId)
                        .ExecuteCommandAsync();
                    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 HandlePalletStockGoodsReturn(List<Dt_StockInfoDetail> palletStockGoods)
        {
@@ -1484,15 +1652,31 @@
                WarehouseId = originalTask.WarehouseId
            };
            // ä¿å­˜å›žåº“任务
            await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
            try
            {
                // ä¿å­˜å›žåº“任务
                var insertcount = await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
                if (insertcount <= 0)
                {
                    throw new Exception("创建任务失败!");
                }
            }
            catch (Exception ex)
            {
                _logger.LogInformation($"CreateReturnTaskAndHandleESS åˆ›å»ºä»»åŠ¡å¤±è´¥: {orderNo} ï¼Œ {palletCode}");
                throw new Exception("创建任务失败!");
            }
            var targetAddress = originalTask.TargetAddress;
            _logger.LogInformation($"CreateReturnTaskAndHandleESS åˆ é™¤åŽ†å²ä»»åŠ¡: {orderNo} ï¼Œ {originalTask.TaskNum}");
            // åˆ é™¤åŽŸå§‹å‡ºåº“ä»»åŠ¡
            _taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
            // await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            // _taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
            var result = _task_HtyService.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.人工删除);
            if (!result)
            {
                await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
            }
            _logger.LogInformation($"CreateReturnTaskAndHandleESS åˆ é™¤åŽ†å²ä»»åŠ¡: {orderNo} ï¼Œ {originalTask.TaskNum},影响行  {result}");
            // ç»™ ESS å‘送流动信号和创建任务
            await SendESSCommands(palletCode, targetAddress, returnTask);
@@ -1509,7 +1693,7 @@
        {
            try
            {
                // 1. å‘送流动信号
                // å‘送流动信号
                var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
                {
                    slotCode = movestations[targetAddress],
@@ -1518,30 +1702,30 @@
                //if (moveResult)
                //{
                // 2. åˆ›å»ºå›žåº“任务
                var essTask = new TaskModel()
                {
                    taskType = "putaway",
                    taskGroupCode = "",
                    groupPriority = 0,
                    tasks = new List<TasksType>{  new() {
                            taskCode = returnTask.TaskNum.ToString(),
                            taskPriority = 0,
                            taskDescribe = new TaskDescribeType
                            {
                                containerCode = palletCode,
                                containerType = "CT_KUBOT_STANDARD",
                                fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
                                toStationCode = "",
                                toLocationCode = returnTask.TargetAddress,
                                deadline = 0,
                                storageTag = ""
                            }
                        } }
                };
                //  åˆ›å»ºå›žåº“任务
                //var essTask = new TaskModel()
                //{
                //    taskType = "putaway",
                //    taskGroupCode = "",
                //    groupPriority = 0,
                //    tasks = new List<TasksType>{  new() {
                //            taskCode = returnTask.TaskNum.ToString(),
                //            taskPriority = 0,
                //            taskDescribe = new TaskDescribeType
                //            {
                //                containerCode = palletCode,
                //                containerType = "CT_KUBOT_STANDARD",
                //                fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
                //                toStationCode = "",
                //                toLocationCode = returnTask.TargetAddress,
                //                deadline = 0,
                //                storageTag = ""
                //            }
                //        } }
                //};
                var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                _logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                //var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
                //_logger.LogInformation($"ReturnRemaining åˆ›å»ºä»»åŠ¡æˆåŠŸ: {resultTask}");
                //}
            }
            catch (Exception ex)
@@ -1566,13 +1750,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>()
@@ -1580,7 +1776,19 @@
                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)
                {
@@ -1593,11 +1801,9 @@
                        .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)
@@ -1606,8 +1812,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
@@ -1655,147 +1923,6 @@
            }
        }
        private async Task HandleOrderCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
        {
            // è°ƒæ‹¨å‡ºåº“和重检出库不需要反馈MES
            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt())
            {
                var allocate = _allocateService.Repository.QueryData(x => x.UpperOrderNo == outboundOrder.UpperOrderNo).First();
                var feedmodel = new AllocateDto
                {
                    ReqCode = Guid.NewGuid().ToString(),
                    ReqTime = DateTime.Now.ToString(),
                    BusinessType = "3",
                    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 AllocateDtoDetail
                   {
                       MaterialCode = group.Key.MaterielCode,
                       LineNo = group.Key.lineNo,
                       WarehouseCode = group.Key.WarehouseCode,
                       Qty = group.Sum(x => x.PickedQty),
                       Unit = group.Key.Unit,
                       Barcodes = group.Select(row => new BarcodeInfo
                       {
                           Barcode = row.CurrentBarcode,
                           SupplyCode = row.SupplyCode,
                           BatchNo = row.BatchNo,
                           Unit = row.Unit,
                           Qty = row.PickedQty
                       }).ToList()
                   }).ToList();
                feedmodel.Details = groupedData;
                var result = await _invokeMESService.FeedbackAllocate(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();
                }
            }
            else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
            {
            }
            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.拣选完成)
                        .ToListAsync();
                    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,
                           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
                           {
                               barcode = row.CurrentBarcode,
                               supplyCode = row.SupplyCode,
                               batchNo = row.BatchNo,
                               unit = row.Unit,
                               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}");
                }
            }
        }
        #endregion
        #region ç©ºæ‰˜ç›˜
@@ -1809,12 +1936,10 @@
            {
                // 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>()
                   .Where(x => x.Id == stockId).ExecuteCommandAsync();
                _stockInfoService.DeleteData(stockId);
                _logger.LogInformation($"清理零库存明细记录 - StockId: {stockId}, åˆ é™¤è®°å½•æ•°: {deleteDetailCount}");
@@ -1834,7 +1959,7 @@
        {
            try
            {
                // 1. æŸ¥æ‰¾æ‰€æœ‰ä¸Žè¯¥è®¢å•和托盘相关的任务
                //  æŸ¥æ‰¾æ‰€æœ‰ä¸Žè¯¥è®¢å•和托盘相关的任务
                var tasks = await _taskRepository.Db.Queryable<Dt_Task>().Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode).ToListAsync();
                if (tasks.Any())
@@ -1887,7 +2012,7 @@
                    _logger.LogInformation($"订单 {orderNo} è¿˜æœ‰å…¶ä»–托盘在处理,不更新订单状态");
                }
                // 3. æ›´æ–°æ‹£é€‰è®°å½•状态(可选)
                // æ›´æ–°æ‹£é€‰è®°å½•状态(可选)
                await UpdatePickingRecordsStatus(orderNo, palletCode);
            }
@@ -1930,8 +2055,6 @@
                _logger.LogInformation($"订单 {orderNo} å·²æ ‡è®°ä¸ºå‡ºåº“完成");
                // å‘MES反馈订单完成(如果需要)
                await HandleOrderCompletion(outboundOrder, orderNo);
            }
        }
@@ -1949,7 +2072,6 @@
                // è¿™é‡Œå¯ä»¥æ ¹æ®éœ€è¦æ›´æ–°æ‹£é€‰è®°å½•的状态字段
                // ä¾‹å¦‚:pickingRecord.Status = (int)PickingStatusEnum.已完成;
                _logger.LogInformation($"找到{pickingRecords.Count}条拣选记录 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
            }
@@ -1959,8 +2081,6 @@
            }
        }
        #endregion
        #region è¾…助方法
        /// <summary>
@@ -2050,89 +2170,6 @@
            return result;
        }
        /// <summary>
        /// æ£€æŸ¥æ‰˜ç›˜æ˜¯å¦ä¸ºç©º
        /// </summary>
        private async Task<bool> IsPalletEmpty(string orderNo, string palletCode)
        {
            try
            {
                // èŽ·å–åº“å­˜ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .Where(x => x.PalletCode == palletCode)
                    .FirstAsync();
                if (stockInfo == null)
                    return false;
                // ä½¿ç”¨ç»Ÿä¸€çš„状态分析
                var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id);
                return statusAnalysis.IsEmptyPallet;
            }
            catch (Exception ex)
            {
                _logger.LogWarning($"检查托盘是否为空失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return false;
            }
        }
        /// <summary>
        /// æ£€æŸ¥å¹¶å¤„理空托盘
        /// </summary>
        private async Task<bool> CheckAndHandleEmptyPallet(string orderNo, string palletCode)
        {
            try
            {
                // 1. èŽ·å–åº“å­˜ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .Where(x => x.PalletCode == palletCode)
                    .FirstAsync();
                if (stockInfo == null)
                {
                    _logger.LogWarning($"未找到托盘 {palletCode} çš„库存信息");
                    return false;
                }
                // 2. ä½¿ç”¨ç»Ÿä¸€çš„状态分析
                var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id);
                // 3. æ£€æŸ¥æ˜¯å¦ä¸ºç©ºæ‰˜ç›˜ä¸”没有进行中的任务
                if (!statusAnalysis.IsEmptyPallet || statusAnalysis.HasActiveTasks)
                {
                    return false;
                }
                _logger.LogInformation($"检测到空托盘,开始自动处理 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                //// æ¸…理零库存数据
                //await CleanupZeroStockData(stockInfo.Id);
                //// æ›´æ–°åº“存主表状态为空托盘
                //await UpdateStockInfoAsEmpty(stockInfo);
                //// å¤„理出库锁定记录
                //await HandleOutStockLockRecords(orderNo, palletCode);
                //// å¤„理任务状态
                //await HandleTaskStatusForEmptyPallet(orderNo, palletCode);
                //// æ›´æ–°è®¢å•数据
                //await UpdateOrderDataForEmptyPallet(orderNo, palletCode);
                ////记录操作历史
                //await RecordAutoEmptyPalletOperation(orderNo, palletCode);
                _logger.LogInformation($"空托盘自动处理完成 - è®¢å•: {orderNo}, æ‰˜ç›˜: {palletCode}");
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError($"自动处理空托盘失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
                return false;
            }
        }
        private async Task<string> GenerateNewBarcode()
        {
            var seq = await _dailySequenceService.GetNextSequenceAsync();
@@ -2168,6 +2205,8 @@
                FactoryArea = originalLock.FactoryArea,
                lineNo = originalLock.lineNo,
                WarehouseCode = originalLock.WarehouseCode,
                BarcodeQty = originalLock.BarcodeQty,
                BarcodeUnit = originalLock.BarcodeUnit,
            };
@@ -2242,11 +2281,6 @@
            }
        }
        private async Task<int> GenerateTaskNumber()
        {
            return await _dailySequenceService.GetNextSequenceAsync();
        }
        private WebResponseContent CreatePickingResponse(PickingResult result, string adjustedReason)
        {
            //if (result.SplitResults.Any())
@@ -2272,8 +2306,1148 @@
            }
            return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
        }
        #endregion
        #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(string outOrder)
        {
            Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == outOrder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x=>x.Details).First();
            if(outboundOrder == null)
            {
                return WebResponseContent.Instance.Error("未找到满足出库条件的出库单");
            }
            if(outboundOrder.IsBatch == 0)
            {
                return WebResponseContent.Instance.Error("该单据不属于分批回传单据,不允许虚拟出入库");
            }
            //先清空单据虚拟出入库数量进行计算
            foreach (var item in outboundOrder.Details)
            {
                item.NoStockOutQty = 0;
                item.documentsNO = "";
            }
            _outboundOrderDetailService.UpdateData(outboundOrder.Details);
            return WebResponseContent.Instance.OK("成功");
        }
    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}");
            }
            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.outOder}");
            }
            var detailLists = new List<Dt_InboundOrderDetail>();
            var matchedDetail = inboundOrder.Details.FirstOrDefault(detail =>
                detail.Barcode == noStockOut.barCode &&
                detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()&& detail.ReceiptQuantity == 0);
            if (matchedDetail == null)
            {
                matchedDetail = inboundOrder.Details.FirstOrDefault(detail =>
                    detail.OutBoxbarcodes == noStockOut.barCode &&
                    detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt() && detail.ReceiptQuantity == 0);
                if (matchedDetail == null)
                {
                    return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} ä¸­æœªæ‰¾åˆ°æ¡ç ä¸º {noStockOut.barCode} çš„可出库明细。");
                }
                else
                {
                    // æ·»åŠ æ‰€æœ‰åŒ¹é…å¤–ç®±ç ä¸”éžå®ŒæˆçŠ¶æ€çš„æ˜Žç»†
                    foreach (var detail in inboundOrder.Details)
                    {
                        if (detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt() &&
                            !string.IsNullOrEmpty(detail.Barcode) &&
                            detail.OutBoxbarcodes == noStockOut.barCode && detail.OrderQuantity > detail.NoStockOutQty)
                        {
                            detailLists.Add(detail);
                        }
                    }
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(noStockOut.barCode))
                {
                    detailLists.Add(matchedDetail);
                }
            }
                if (!detailLists.Any())
                {
                    return WebResponseContent.Instance.Error("该条码已经没有可出库数量");
                }
                var outDetails = new List<Dt_OutboundOrderDetail>();
                // éåŽ†æ¯ä¸ªå…¥åº“æ˜Žç»†
                foreach (var item in detailLists)
                {
                    // é‡ç½®å½“前入库明细的无库存出库数量
                    item.NoStockOutQty = 0;
                    // å½“前条码需要出库的总数量(入库单剩余可出数量)
                    decimal remainingBarcodeQty = item.OrderQuantity - item.ReceiptQuantity;
                    if (remainingBarcodeQty <= 0)
                    {
                        return WebResponseContent.Instance.Error($"该采购单中的条码{item.Barcode}对应的可出数量为0");
                    }
                    // ç­›é€‰å‡ºåº“单中符合条件的明细(同物料、非完成、有剩余可出数量)
                    var eligibleOutDetails = outboundOrder.Details.Where(detail =>
                        detail.MaterielCode == item.MaterielCode &&
                        detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt() &&
                        (detail.OrderQuantity - detail.LockQuantity - detail.MoveQty - detail.NoStockOutQty) > 0).ToList();
                    if (!eligibleOutDetails.Any())
                    {
                        return WebResponseContent.Instance.Error($"在出库单中未找到物料{item.MaterielCode}的可出库明细");
                    }
                    // éåŽ†ç¬¦åˆæ¡ä»¶çš„å‡ºåº“æ˜Žç»†ï¼Œé€è¡Œåˆ†é…æ•°é‡
                    foreach (var outDetail in eligibleOutDetails)
                    {
                        // è®¡ç®—当前出库行的剩余可出数量
                        decimal rowRemainingQty = outDetail.OrderQuantity - outDetail.LockQuantity - outDetail.MoveQty - outDetail.NoStockOutQty;
                        if (rowRemainingQty <= 0) continue;
                        if (!outDetail.BatchNo.IsNullOrEmpty())
                        {
                            if (outDetail.BatchNo != item.BatchNo)
                            {
                                return WebResponseContent.Instance.Error($"出库单行批次{outDetail.BatchNo}与采购单批次{item.BatchNo}不匹配");
                            }
                        }
                        if (!outDetail.SupplyCode.IsNullOrEmpty())
                        {
                            if (outDetail.SupplyCode != item.SupplyCode)
                            {
                                return WebResponseContent.Instance.Error($"出库单行供应商{outDetail.SupplyCode}与采购单供应商{item.SupplyCode}不匹配");
                            }
                        }
                        if (!string.IsNullOrEmpty(outboundOrder.FactoryArea) && !string.IsNullOrEmpty(inboundOrder.FactoryArea))
                        {
                            if (inboundOrder.FactoryArea != outboundOrder.FactoryArea)
                            {
                                return WebResponseContent.Instance.Error($"该条码{item.Barcode}对应的单据厂区与出库单据不一致!不允许出库。");
                            }
                        }
                        if (inboundOrder.BusinessType != "11" && !outDetail.WarehouseCode.IsNullOrEmpty())
                        {
                            if (outDetail.WarehouseCode != item.WarehouseCode)
                            {
                                return WebResponseContent.Instance.Error($"仓库不一致!出库单行仓库{outDetail.WarehouseCode}与采购单仓库{item.WarehouseCode}不匹配");
                            }
                        }
                        else
                        {
                            item.WarehouseCode = outDetail.WarehouseCode;
                        }
                        // è®¡ç®—本次分配的数量(取剩余条码数量和当前行剩余数量的较小值)
                        decimal assignQty = Math.Min(remainingBarcodeQty, rowRemainingQty);
                        // æ›´æ–°å…¥åº“明细和出库明细的无库存出库数量
                        item.NoStockOutQty += assignQty;
                        outDetail.NoStockOutQty += assignQty;
                        // æ›´æ–°å‰©ä½™éœ€è¦åˆ†é…çš„æ¡ç æ•°é‡
                        remainingBarcodeQty -= assignQty;
                        // è®°å½•已更新的出库明细(去重)
                        if (!outDetails.Contains(outDetail))
                        {
                            outDetails.Add(outDetail);
                        }
                        // éªŒè¯å½“前行是否溢出
                        if ((outDetail.LockQuantity + outDetail.NoStockOutQty + outDetail.MoveQty) > outDetail.OrderQuantity)
                        {
                            return WebResponseContent.Instance.Error($"出库单明细{outDetail.Id}数量溢出,超出数量:{(outDetail.LockQuantity + outDetail.NoStockOutQty + outDetail.MoveQty) - outDetail.OrderQuantity}");
                        }
                        // å¤„理MES参数回传:记录当前条码分配到该行的实际数量
                        List<Barcodes> barcodesList = new List<Barcodes>();
                        Barcodes barcodes = new Barcodes
                        {
                            Barcode = item.Barcode,
                            Qty = assignQty,
                            SupplyCode = item?.SupplyCode ?? "",
                            BatchNo = item?.BatchNo ?? "",
                            Unit = item?.Unit ?? ""
                        };
                        // ååºåˆ—化该行已有的条码记录
                        if (!string.IsNullOrEmpty(outDetail.documentsNO))
                        {
                            try
                            {
                                barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(outDetail.documentsNO) ?? new List<Barcodes>();
                            }
                            catch (JsonException ex)
                            {
                                return WebResponseContent.Instance.Error($"出库单明细{outDetail.Id}的documentsNO字段格式错误:{ex.Message}");
                            }
                        }
                        // æ·»åŠ æœ¬æ¬¡åˆ†é…çš„è®°å½•
                        barcodesList.Add(barcodes);
                        // åºåˆ—化回存
                        JsonSerializerSettings settings = new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        };
                        outDetail.documentsNO = JsonConvert.SerializeObject(barcodesList, settings);
                        // æ¡ç æ•°é‡åˆ†é…å®Œæ¯•,退出当前行循环
                        if (remainingBarcodeQty <= 0)
                        {
                            break;
                        }
                    }
                    // æ‰€æœ‰ç¬¦åˆæ¡ä»¶çš„出库行遍历完后,条码数量仍有剩余
                    if (remainingBarcodeQty > 0)
                    {
                        return WebResponseContent.Instance.Error($"条码{item.Barcode}需出库数量{item.OrderQuantity - item.ReceiptQuantity},但出库单中物料{item.MaterielCode}剩余可出总量不足,仍剩余{remainingBarcodeQty}数量未分配");
                    }
                }
                _unitOfWorkManage.BeginTran();
            _inboundOrderDetailService.UpdateData(detailLists);
            _outboundOrderDetailService.UpdateData(outDetails);
            _unitOfWorkManage.CommitTran();
            return WebResponseContent.Instance.OK("成功", data: detailLists);
        }
        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} çš„æ˜Žç»†ã€‚");
            }
            // é‡ç½®å…¥åº“明细的无库存出库数量
            decimal revokedTotalQty = matchedDetail.NoStockOutQty; // è®°å½•需要撤销的总数量(入库单中已分配的数量)
            matchedDetail.NoStockOutQty = 0;
            if(inboundOrder.BusinessType == "11")
            {
                matchedDetail.WarehouseCode ="";
            }
            // é‡ç½®å…¥åº“明细状态
            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.outOder}");
            }
            // æ‰¾åˆ°æ‰€æœ‰å…³è”该条码的出库明细行
            // åŒç‰©æ–™ã€éžå®ŒæˆçŠ¶æ€ã€documentsNO包含该条码
            var matchedCodeList = outboundOrder.Details.Where(detail =>
                detail.MaterielCode == matchedDetail.MaterielCode && // ç¡®ä¿ç‰©æ–™åŒ¹é…
                detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt() &&
                !string.IsNullOrEmpty(detail.documentsNO) &&
                detail.documentsNO.Contains(noStockOut.barCode) // åŒ…含当前条码
            ).ToList();
            if (!matchedCodeList.Any())
            {
                return WebResponseContent.Instance.Error($"在出库单中未找到关联条码{noStockOut.barCode}的物料{matchedDetail.MaterielCode}明细。");
            }
            //逐行处理出库明细的撤销逻辑
            decimal remainingRevokeQty = revokedTotalQty; // å‰©ä½™éœ€è¦æ’¤é”€çš„æ•°é‡
            foreach (var matchedCode in matchedCodeList)
            {
                if (remainingRevokeQty <= 0) break; // æ‰€æœ‰æ•°é‡å·²æ’¤é”€ï¼Œé€€å‡ºå¾ªçޝ
                // å¤„理MES参数撤销
                List<Barcodes> barcodesList = new List<Barcodes>();
                if (!string.IsNullOrEmpty(matchedCode.documentsNO))
                {
                    try
                    {
                        barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(matchedCode.documentsNO) ?? new List<Barcodes>();
                    }
                    catch (JsonException ex)
                    {
                        return WebResponseContent.Instance.Error($"出库单明细{matchedCode.Id}的documentsNO字段格式错误:{ex.Message}");
                    }
                }
                // ç­›é€‰å‡ºå½“前条码的所有记录
                var barcodeRecords = barcodesList.Where(b =>
                    string.Equals(b.Barcode, noStockOut.barCode, StringComparison.OrdinalIgnoreCase)).ToList();
                if (!barcodeRecords.Any()) continue;
                // è®¡ç®—该行需要撤销的数量(累加该条码在该行的所有分配数量)
                decimal rowRevokeQty = barcodeRecords.Sum(b => b.Qty);
                // å®žé™…撤销数量:取该行可撤销数量和剩余需要撤销数量的较小值
                decimal actualRevokeQty = Math.Min(rowRevokeQty, remainingRevokeQty);
                // ç§»é™¤è¯¥è¡Œä¸­è¯¥æ¡ç çš„记录(或部分记录,若剩余撤销数量不足)
                if (actualRevokeQty < rowRevokeQty)
                {
                    // å‰©ä½™æ’¤é”€æ•°é‡ä¸è¶³ï¼Œåªç§»é™¤éƒ¨åˆ†è®°å½•(按数量扣减)
                    decimal tempQty = actualRevokeQty;
                    var removeList = new List<Barcodes>();
                    foreach (var record in barcodeRecords)
                    {
                        if (tempQty <= 0) break;
                        if (record.Qty <= tempQty)
                        {
                            removeList.Add(record);
                            tempQty -= record.Qty;
                        }
                        else
                        {
                            // è®°å½•数量拆分,扣减部分数量
                            record.Qty -= tempQty;
                            tempQty = 0;
                        }
                    }
                    barcodesList.RemoveAll(b => removeList.Contains(b));
                }
                else
                {
                    // ç§»é™¤è¯¥è¡Œä¸­è¯¥æ¡ç çš„æ‰€æœ‰è®°å½•
                    barcodesList.RemoveAll(b =>
                        string.Equals(b.Barcode, noStockOut.barCode, StringComparison.OrdinalIgnoreCase));
                }
                //重新序列化MES参数
                JsonSerializerSettings settings = new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                };
                matchedCode.documentsNO = JsonConvert.SerializeObject(barcodesList, settings);
                //扣减出库明细的无库存出库数量
                matchedCode.NoStockOutQty = Math.Max(0, matchedCode.NoStockOutQty - actualRevokeQty);
                remainingRevokeQty -= actualRevokeQty;
                //重置出库明细状态
                if (matchedCode.LockQuantity == 0 && matchedCode.OverOutQuantity == 0)
                {
                    matchedCode.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt();
                }
            }
            //若仍有未撤销的数量,说明数据不一致
            if (remainingRevokeQty > 0)
            {
                return WebResponseContent.Instance.Error($"撤销条码{noStockOut.barCode}时,出库单中可撤销数量不足,仍有{remainingRevokeQty}数量未撤销");
            }
            _unitOfWorkManage.BeginTran();
            _inboundOrderDetailService.UpdateData(matchedDetail);
            _outboundOrderDetailService.UpdateData(matchedCodeList);
            _unitOfWorkManage.CommitTran();
            return WebResponseContent.Instance.OK("条码撤销成功", data: new { RevokedQty = revokedTotalQty });
        }
        catch (Exception ex)
        {
            _unitOfWorkManage.RollbackTran();
            return WebResponseContent.Instance.Error(ex.Message);
        }
    }
    public async Task<WebResponseContent> NoStockOutSubmit(NoStockOutSubmit noStockOutSubmit)
        {
            try
            {
                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_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
                Dictionary<int, List<string>> orderIdBarCodeDict = new Dictionary<int, List<string>>();
                List<Dt_InboundOrderDetail> updateInboundDetails = new List<Dt_InboundOrderDetail>();
                Dictionary<int, Dt_InboundOrder> updateInboundOrders = new Dictionary<int, Dt_InboundOrder>();
                List<Dt_StockQuantityChangeRecord> changeRecords = new List<Dt_StockQuantityChangeRecord>();
                _unitOfWorkManage.BeginTran();
                List<Dt_InboundOrderDetail> allInboundDetails = _inboundOrderDetailService.Db
                .Queryable<Dt_InboundOrderDetail>()
                .Where(detail => noStockOutSubmit.BarCodeSubmit.Contains(detail.Barcode)
                    && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt())
                .ToList();
                var detailGroupByOrderId = allInboundDetails.GroupBy(d => d.OrderId).ToList();
                foreach (var group in detailGroupByOrderId)
                {
                    int orderId = group.Key;
                    List<Dt_InboundOrderDetail> groupDetails = group.ToList();
                    List<string> groupBarCodes = groupDetails.Select(d => d.Barcode).ToList();
                    orderIdBarCodeDict[orderId] = groupBarCodes;
                    Dt_InboundOrder currentInboundOrder = null;
                    if (!updateInboundOrders.TryGetValue(orderId, out currentInboundOrder))
                    {
                        currentInboundOrder = _inboundOrderRepository.Db
                            .Queryable<Dt_InboundOrder>()
                            .Where(x => x.Id == orderId)
                            .Includes(x => x.Details)
                            .First();
                        if (currentInboundOrder == null)
                        {
                            _unitOfWorkManage.RollbackTran();
                            return WebResponseContent.Instance.Error($"未找到入库单ID为 {orderId} çš„单据");
                        }
                        updateInboundOrders[orderId] = currentInboundOrder;
                    }
                    foreach (var detail in groupDetails)
                    {
                        detail.ReceiptQuantity = detail.NoStockOutQty;
                        detail.OverInQuantity = detail.NoStockOutQty;
                        if (detail.OrderQuantity == detail.OverInQuantity)
                        {
                            detail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
                        }
                        updateInboundDetails.Add(detail);
                        //添加库存变动记录
                        Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord
                        {
                            StockDetailId = detail.Id,
                            PalleCode = DateTime.Now.ToString(),
                            MaterielCode = detail.MaterielCode,
                            MaterielName = detail.MaterielName ?? "",
                            BatchNo = detail.BatchNo ?? "",
                            OriginalSerilNumber = detail.Barcode,
                            NewSerilNumber = "",
                            OrderNo = currentInboundOrder.InboundOrderNo,
                            TaskNum = 0,
                            ChangeType = (int)StockChangeTypeEnum.Inbound,
                            ChangeQuantity = detail.NoStockOutQty,
                            BeforeQuantity = detail.OverInQuantity - detail.NoStockOutQty,
                            AfterQuantity = detail.OverInQuantity,
                            SupplyCode = detail.SupplyCode ?? "",
                            WarehouseCode = detail.WarehouseCode ?? "",
                            Remark = $"虚拟入库"
                        };
                        changeRecords.Add(changeRecord);
                    }
                    var inboundOrder = updateInboundOrders[orderId];
                    int totalDetailCount = inboundOrder.Details.Count;
                    int finishedDetailCount = inboundOrder.Details.Count(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt())
                                             + groupDetails.Count(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt());
                    inboundOrder.OrderStatus = totalDetailCount == finishedDetailCount
                        ? InOrderStatusEnum.入库完成.ObjToInt()
                        : InOrderStatusEnum.入库中.ObjToInt();
                }
                if (updateInboundDetails.Any())
                {
                    _inboundOrderDetailService.Db.Updateable(updateInboundDetails).ExecuteCommand();
                }
                if (updateInboundOrders.Any())
                {
                    // æ‰¹é‡æ›´æ–°å…¥åº“主单
                    _inboundOrderService.Db.Updateable(updateInboundOrders.Values.ToList()).ExecuteCommand();
                }
                // 7. å¾ªçŽ¯åˆ†ç»„ç»“æžœï¼Œè°ƒç”¨MES回传方法(按入库单分组回传)
                foreach (var kvp in orderIdBarCodeDict)
                {
                    int orderId = kvp.Key;
                    List<string> barCodeList = kvp.Value;
                    //入库回传MES
                    NoStockOutBatchInOrderFeedbackToMes(orderId, barCodeList);
                }
                //只对出库条码的出库单明细进行计算回传
                List<Dt_OutboundOrderDetail> outboundOrderDetail = outboundOrder.Details
                .Where(x => !string.IsNullOrWhiteSpace(x.documentsNO)
                    && noStockOutSubmit.BarCodeSubmit.Any(barcode =>
                        x.documentsNO.IndexOf(barcode, StringComparison.OrdinalIgnoreCase) >= 0))
                .ToList();
                foreach (var item in outboundOrderDetail)
                {
                    item.LockQuantity += item.NoStockOutQty;
                    item.OverOutQuantity += item.NoStockOutQty;
                    item.CurrentDeliveryQty += item.NoStockOutQty;
                    //添加回传MES参数
                    List<Barcodes> barcodesList = new List<Barcodes>();
                    List<Barcodes> documentsNOList = new List<Barcodes>();
                    if (!string.IsNullOrEmpty(item.ReturnJsonData))
                    {
                        barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(item.ReturnJsonData) ?? new List<Barcodes>();
                    }
                    if (!string.IsNullOrEmpty(item.documentsNO) && item.documentsNO!="")
                    {
                        documentsNOList = JsonConvert.DeserializeObject<List<Barcodes>>(item.documentsNO) ?? new List<Barcodes>();
                    }
                    foreach (var documentsNO in documentsNOList)
                    {
                        barcodesList.Add(documentsNO);
                    }
                    JsonSerializerSettings settings = new JsonSerializerSettings
                    {
                        ContractResolver = new CamelCasePropertyNamesContractResolver()
                    };
                    item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings);
                    //添加库存变动记录
                    Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord
                    {
                        StockDetailId = item.Id,
                        PalleCode = DateTime.Now.ToString(),
                        MaterielCode = item.MaterielCode,
                        MaterielName = item.MaterielName ?? "",
                        BatchNo = item.BatchNo ?? "",
                        OriginalSerilNumber = item.ReturnJsonData,
                        NewSerilNumber = "",
                        OrderNo = outboundOrder.OrderNo,
                        TaskNum = 0,
                        ChangeType = (int)StockChangeTypeEnum.Inbound,
                        ChangeQuantity = -item.NoStockOutQty,
                        BeforeQuantity = item.OrderQuantity,
                        AfterQuantity = item.OrderQuantity - item.OverOutQuantity,
                        SupplyCode = item.SupplyCode ?? "",
                        WarehouseCode = item.WarehouseCode ?? "",
                        Remark = $"虚拟出库"
                    };
                    changeRecords.Add(changeRecord);
                    outboundOrderDetails.Add(item);
                }
                _outboundOrderDetailService.UpdateData(outboundOrderDetails);
                //批量添加库存变动记录
                if (changeRecords.Any())
                {
                    _stockQuantityChangeRecordService.Db.Insertable(changeRecords).ExecuteCommand();
                }
                // æ£€æŸ¥å‡ºåº“单是否完成
                if (CheckOutboundOrderCompleted(outboundOrder.OrderNo))
                {
                    outboundOrder.OrderStatus = OutOrderStatusEnum.出库完成.ObjToInt();
                }
                else
                {
                    outboundOrder.OrderStatus = OutOrderStatusEnum.出库中.ObjToInt();
                }
                _outboundOrderService.UpdateData(outboundOrder);
                _unitOfWorkManage.CommitTran();
                //出库回传MES
                _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                return WebResponseContent.Instance.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        public WebResponseContent NoStockOutBatchInOrderFeedbackToMes(int id,List<string> barCodeList)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>()
                                    .Where(x => x.Id == id)
                                    .First();
                List<Dt_MesReturnRecord> returnRecords = _mesReturnRecord.QueryData(x => x.OrderNo == inboundOrder.InboundOrderNo && x.OrderId == inboundOrder.Id && x.ReturnStatus == 2);
                foreach (var item in returnRecords)
                {
                    HttpResponseResult<MesResponseDTO> httpResponse = _httpClientHelper.Post<MesResponseDTO>(item.ApiUrl, item.RequestData);
                    item.ReturnCount += 1;
                    bool success = httpResponse.IsSuccess && httpResponse.Data.Code == "200";
                    item.ReturnStatus = success ? 1 : 2;
                    item.HttpStatusCode = httpResponse.StatusCode.ObjToInt();
                    item.LastReturnTime = DateTime.Now;
                    item.ResponseData = httpResponse.Content;
                    item.SuccessTime = httpResponse.IsSuccess ? DateTime.Now : null;
                    //List<Dt_InboundOrderDetail> details=new List<Dt_InboundOrderDetail>();
                    //foreach (var y in item.DetailsId.Split(','))
                    //{
                    //    details.Add( _inboundOrderDetailRepository.QueryFirst(x => x.Id == Convert.ToInt32(y)));
                    //}
                }
                _mesReturnRecord.UpdateData(returnRecords);
                var inboundOrderDetail = _inboundOrderRepository.Db.Queryable<Dt_InboundOrderDetail>()
                                        .Where(x => x.OrderId == inboundOrder.Id && x.OrderDetailStatus == (int)OrderDetailStatusEnum.Over && x.ReturnToMESStatus == 0)
                                        .ToList();
                var detail = inboundOrderDetail.Where(x => barCodeList.Contains(x.Barcode)).ToList();
                if (inboundOrder.OrderType == (int)InOrderTypeEnum.AllocatInbound)//调拨入库
                {
                    var allocate = _inboundOrderRepository.Db.Queryable<Dt_AllocateOrder>().Where(x => x.OrderNo == inboundOrder.InboundOrderNo).First();
                    var allocatefeedmodel = new AllocateDto
                    {
                        ReqCode = Guid.NewGuid().ToString(),
                        ReqTime = DateTime.Now.ToString(),
                        BusinessType = "3",
                        FactoryArea = inboundOrder.FactoryArea,
                        OperationType = 1,
                        Operator = App.User.UserName,
                        OrderNo = inboundOrder.UpperOrderNo,
                        fromWarehouse = allocate?.FromWarehouse ?? "",
                        toWarehouse = allocate?.ToWarehouse ?? "",
                        Details = NoStockOutGetAllocateDtoDetails(detail)
                    };
                    if (allocatefeedmodel.Details.Count <= 0)
                    {
                        throw new Exception("未找到需要回传的数据");
                    }
                    var response = NoStockOutresponseModel(inboundOrder, 3, null, allocatefeedmodel);
                    if (response != null && response.IsSuccess && response.Data.Code == "200")
                    {
                        _inboundOrderRepository.Db.Updateable<Dt_InboundOrderDetail>().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 })
                            .Where(it => it.OrderId == inboundOrder.Id && barCodeList.Contains(it.Barcode)).ExecuteCommand();
                        if (inboundOrder.OrderStatus == InOrderStatusEnum.入库完成.ObjToInt())
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        else if (inboundOrder.OrderStatus == InOrderStatusEnum.入库中.ObjToInt())
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 3 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        else
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 0 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                    }
                    else
                    {
                        _inboundOrderRepository.Db.Updateable<Dt_InboundOrderDetail>().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 2 })
                            .Where(it => it.OrderId == inboundOrder.Id && barCodeList.Contains(it.Barcode)).ExecuteCommand();
                        if (inboundOrder.OrderStatus == InOrderStatusEnum.入库完成.ObjToInt())
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 2 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        else if (inboundOrder.OrderStatus == InOrderStatusEnum.入库中.ObjToInt())
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 4 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        else
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 0 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        return content.Error("回传MES失败");
                    }
                }
                else
                {
                    var feedmodel = 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 = NoStockOutFeedbackInboundDetailsModelDto(detail)
                    };
                    if (feedmodel.details.Count <= 0)
                    {
                        throw new Exception("未找到需要回传的数据");
                    }
                    var response = NoStockOutresponseModel(inboundOrder, 3, feedmodel);
                    if (response != null && response.IsSuccess && response.Data.Code == "200")
                    {
                        _inboundOrderRepository.Db.Updateable<Dt_InboundOrderDetail>().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 })
                            .Where(it => it.OrderId == inboundOrder.Id && barCodeList.Contains(it.Barcode)).ExecuteCommand();
                        if (inboundOrder.OrderStatus == InOrderStatusEnum.入库完成.ObjToInt())
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        else if (inboundOrder.OrderStatus == InOrderStatusEnum.入库中.ObjToInt())
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 3 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        else
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 0 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                    }
                    else
                    {
                        _inboundOrderRepository.Db.Updateable<Dt_InboundOrderDetail>().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 2 }).Where(it => it.OrderId == inboundOrder.Id && barCodeList.Contains(it.Barcode)).ExecuteCommand();
                        if (inboundOrder.OrderStatus == InOrderStatusEnum.入库完成.ObjToInt())
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 2 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        else if (inboundOrder.OrderStatus == InOrderStatusEnum.入库中.ObjToInt())
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 4 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        else
                        {
                            _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 0 })
                                .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
                        }
                        return content.Error("回传MES失败");
                    }
                }
                return content.OK("回传MES成功");
            }
            catch (Exception ex)
            {
                return content.Error(ex.Message);
            }
        }
        public List<AllocateDtoDetail> NoStockOutGetAllocateDtoDetails(List<Dt_InboundOrderDetail> inboundOrderDetails)
        {
            var groupedData = inboundOrderDetails.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.BarcodeQty),
                   Unit = group.Key.BarcodeUnit,
                   Barcodes = group.Select(row => new BarcodeInfo
                   {
                       Barcode = row.Barcode,
                       Qty = row.BarcodeQty,
                       BatchNo = row.BatchNo,
                       SupplyCode = row.SupplyCode,
                       Unit = row.BarcodeUnit
                   }).ToList()
               }).ToList();
            return groupedData;
        }
        public List<FeedbackInboundDetailsModel> NoStockOutFeedbackInboundDetailsModelDto(List<Dt_InboundOrderDetail> inboundOrderDetails )
        {
            var groupedData = inboundOrderDetails.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
               .Select(group => new FeedbackInboundDetailsModel
               {
                   materialCode = group.Key.MaterielCode,
                   lineNo = group.Key.lineNo,
                   warehouseCode = group.Key.WarehouseCode,
                   qty = group.Sum(x => x.BarcodeQty),
                   unit = group.Key.BarcodeUnit,
                   barcodes = group.Select(row => new FeedbackBarcodesModel
                   {
                       barcode = row.Barcode,
                       qty = row.BarcodeQty
                   }).ToList()
               }).ToList();
            return groupedData;
        }
        public HttpResponseResult<MesResponseDTO> NoStockOutresponseModel(Dt_InboundOrder order, int InterfaceType, FeedbackInboundRequestModel model = null, AllocateDto allocateDto = null)
        {
            HttpResponseResult<MesResponseDTO> httpResponseResult = new HttpResponseResult<MesResponseDTO>();
            string reqCode = Guid.NewGuid().ToString();
            string reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            string requestData = string.Empty;
            string apiUrl = string.Empty;
            if (model != null)
            {
                apiUrl = AppSettings.GetValue("AldMaterialWarehousing");
                httpResponseResult = _httpClientHelper.Post<MesResponseDTO>(apiUrl, model.Serialize());
                requestData = model.Serialize();
            }
            else
            {
                apiUrl = AppSettings.GetValue("AldAllocationOperation");
                httpResponseResult = _httpClientHelper.Post<MesResponseDTO>(apiUrl, allocateDto.Serialize());
                requestData = allocateDto.Serialize();
            }
            httpResponseResult.ApiUrl = apiUrl;
            bool isSuccess = httpResponseResult.IsSuccess && httpResponseResult.Data.Code == "200";
            string message = "成功";
            if (!isSuccess)
            {
                if (!httpResponseResult.IsSuccess)
                {
                    message = $"MES接口返回错误,HTTP代码:{httpResponseResult.StatusCode},信息:{httpResponseResult.ErrorMessage}";
                }
                else if (httpResponseResult?.Data?.Code != "200")
                {
                    message = $"调用MES接口失败,代码:{httpResponseResult?.Data?.Code},信息:{httpResponseResult?.Data?.Message}";
                }
            }
            Dt_MesReturnRecord mesReturnRecord = new Dt_MesReturnRecord()
            {
                ApiUrl = httpResponseResult.ApiUrl,
                InterfaceType = InterfaceType,
                OrderId = order.Id,
                OrderNo = order.InboundOrderNo,
                RequestCode = reqCode,
                RequestData = requestData,
                FailureReason = message,
                LastReturnTime = DateTime.Now,
                HttpStatusCode = httpResponseResult.StatusCode.ObjToInt(),
                ResponseData = httpResponseResult.Content,
                ReturnType = 0,
                ReturnCount = 1,
                ReturnStatus = httpResponseResult.IsSuccess ? 1 : 2,
                SuccessTime = httpResponseResult.IsSuccess ? DateTime.Now : null
            };
            _unitOfWorkManage.Db.Insertable(mesReturnRecord).ExecuteCommand();
            return httpResponseResult;
        }
        public bool CheckOutboundOrderCompleted(string orderNo)
        {
            Dt_OutboundOrder outboundOrder = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().Where(x => x.OrderNo == orderNo).First();
            if (outboundOrder == null) return false;
            List<Dt_OutboundOrderDetail> details = _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().Where(x => x.OrderId == outboundOrder.Id).ToList();
            // æ£€æŸ¥æ‰€æœ‰æ˜Žç»†çš„已出数量是否都等于单据数量
            return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty);
        }
        public WebResponseContent GetPurchaseOrderByBarcode(string barcode)
        {
            try
            {
                Dt_InboundOrderDetail inboundOrderDetail = _stockInfoDetailService.Db.Queryable<Dt_InboundOrderDetail>().Where(x => x.Barcode == barcode && x.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()).First();
                if (inboundOrderDetail == null)
                {
                    inboundOrderDetail = _stockInfoDetailService.Db.Queryable<Dt_InboundOrderDetail>().Where(x => x.OutBoxbarcodes == barcode && x.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt()).First();
                    if(inboundOrderDetail == null)
                    {
                        return WebResponseContent.Instance.Error($"未找到该条码{barcode}的入库明细或者明细状态已入智仓完成");
                    }
                }
                Dt_InboundOrder inboundOrder = _inboundOrderRepository.QueryFirst(x => x.Id == inboundOrderDetail.OrderId && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt());
                if (inboundOrder == null)
                {
                    return WebResponseContent.Instance.Error($"该入库单{inboundOrder}状态已入智仓完成");
                }
                var resultData = new { purchaseOrderNo = inboundOrder.UpperOrderNo };
                return WebResponseContent.Instance.OK("查询采购单成功", data: resultData);
            }
            catch (Exception ex)
            {
                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 || dborder.SignSeq == 1))
                    {
                        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 orderno = dbinboundOrderDetails.First().OrderNo;
                var dbinbound = _inboundOrderRepository.QueryData(x => x.InboundOrderNo == orderno).FirstOrDefault();
                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);
        }
    }
    #region æ”¯æŒç±»å®šä¹‰
@@ -2312,20 +3486,6 @@
        public List<SplitResult> SplitResults { get; set; } = new List<SplitResult>();
    }
    public class ReturnAnalysisResult
    {
        public bool HasItemsToReturn { get; set; }
        public bool HasRemainingLocks { get; set; }
        public bool HasPalletStockGoods { get; set; }
        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 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 class PalletStatusAnalysis
    {
        public string OrderNo { get; set; }
@@ -2339,12 +3499,22 @@
        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 HashSet<string> ProcessedBarcodes { get; set; } = new HashSet<string>();
        public List<string> AllBarcodes { get; set; } = new List<string>();
        // ç©ºæ‰˜ç›˜ç›¸å…³å±žæ€§
        public bool IsEmptyPallet { get; set; }
        public bool HasActiveTasks { get; set; }
@@ -2353,18 +3523,7 @@
        public bool CanReturn => HasItemsToReturn && !HasActiveTasks;
        public bool CanRemove => IsEmptyPallet && !HasActiveTasks;
    }
    public class PickingContext
    {
        public string OrderNo { get; set; }
        public string PalletCode { get; set; }
        public string Barcode { get; set; }
        public string Operator { get; set; }
        public Dt_OutStockLockInfo LockInfo { get; set; }
        public Dt_OutboundOrderDetail OrderDetail { get; set; }
        public Dt_StockInfoDetail StockDetail { get; set; }
        public decimal ActualQuantity { get; set; }
        public string AdjustedReason { get; set; }
    }
    public class CancelPickingContext
    {
        public string OrderNo { get; set; }