1
647556386
2026-02-06 28e67084df6e36f7b3ac3b914d97fa51acb21f8f
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -3,6 +3,7 @@
using Dm.filter;
using MailKit.Search;
using Mapster;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Org.BouncyCastle.Asn1.Ocsp;
@@ -18,14 +19,17 @@
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.CodeConfigEnum;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Base;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.CalcOut;
using WIDESEA_DTO.Outbound;
using WIDESEA_DTO.ReturnMES;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_IRecordService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Check;
using static HslCommunication.Profinet.Knx.KnxCode;
namespace WIDESEA_OutboundService
@@ -59,6 +63,8 @@
        private readonly IESSApiService _eSSApiService;
        private readonly IRepository<Dt_AllocateOrder> _allocateOrderRepository;
        private readonly IRepository<Dt_AllocateMaterialInfo> _allocateMaterialInfoRepository;
        public readonly IRepository<Dt_InboundOrderDetail> _inboundOrderDetailRepository;
        public readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
        private Dictionary<string, string> stations = new Dictionary<string, string>
        {
@@ -72,7 +78,7 @@
            {"3-1","3-5" },
        };
        public OutboundService(IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_OutboundOrderDetail> detailRepository, IRepository<Dt_OutboundOrder> outboundRepository, IRepository<Dt_OutStockLockInfo> outboundLockInfoRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_StockInfoDetail> stockDetailRepository, IRepository<Dt_StockQuantityChangeRecord> stockChangeRepository, IRepository<Dt_StockInfoDetail_Hty> stockDetailHistoryRepository, IBasicService basicService, IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService, IFeedbackMesService feedbackMesService, IRepository<Dt_Task> taskRepository, ILocationInfoService locationInfoService, IESSApiService eSSApiService, IRepository<Dt_AllocateOrder> allocateOrderRepository, IRepository<Dt_AllocateMaterialInfo> allocateMaterialInfoRepository)
        public OutboundService(IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_OutboundOrderDetail> detailRepository, IRepository<Dt_OutboundOrder> outboundRepository, IRepository<Dt_OutStockLockInfo> outboundLockInfoRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_StockInfoDetail> stockDetailRepository, IRepository<Dt_StockQuantityChangeRecord> stockChangeRepository, IRepository<Dt_StockInfoDetail_Hty> stockDetailHistoryRepository, IBasicService basicService, IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService, IFeedbackMesService feedbackMesService, IRepository<Dt_Task> taskRepository, ILocationInfoService locationInfoService, IESSApiService eSSApiService, IRepository<Dt_AllocateOrder> allocateOrderRepository, IRepository<Dt_AllocateMaterialInfo> allocateMaterialInfoRepository, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IRepository<Dt_InboundOrder> inboundOrderRepository)
        {
            _mapper = mapper;
            _unitOfWorkManage = unitOfWorkManage;
@@ -95,6 +101,35 @@
            _eSSApiService = eSSApiService;
            _allocateOrderRepository = allocateOrderRepository;
            _allocateMaterialInfoRepository = allocateMaterialInfoRepository;
            _inboundOrderDetailRepository = inboundOrderDetailRepository;
            _inboundOrderRepository = inboundOrderRepository;
        }
        public WebResponseContent PrintFromData (string barcode)
        {
            var detail = _inboundOrderDetailRepository.QueryFirst(x => x.Barcode == barcode);
            if(detail == null)
            {
                return WebResponseContent.Instance.Error();
            }
            var inbound = _inboundOrderRepository.QueryFirst(x=>x.Id ==  detail.OrderId);
            if(inbound == null)
            {
                return WebResponseContent.Instance.Error();
            }
            var printFormData = new PrintFromDataDTO {
                materialCode = detail.MaterielCode,
                materialName = detail.MaterielName,
                materialSpec = detail.Unit,
                batchNo = detail.BatchNo,
                pruchaseOrderNo = inbound.UpperOrderNo,
                factoryArea = inbound.FactoryArea,
                suplierCode = detail.SupplyCode,
                quantity = detail.BarcodeQty
            };
            return WebResponseContent.Instance.OK(data:printFormData);
        }
        #region å‡ºåº“分配
@@ -106,138 +141,132 @@
        public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request)
        {
            WebResponseContent content = WebResponseContent.Instance;
            PickingOutboundResponseDTO response = new PickingOutboundResponseDTO();
            decimal totalNeedAllocate = 0; // æ€»éœ€æ±‚分配量
            decimal totalActualAllocate = 0; // å®žé™…总分配量
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. è®¡ç®—出库数量逻辑
                OutboundCalculationDTO calculationResult = CalcOutboundQuantity(request);
                if (!calculationResult.CanOutbound)
                {
                    content = WebResponseContent.Instance.Error("无法处理拣货出库:" + calculationResult.ErrorMessage);
                    _unitOfWorkManage.RollbackTran();
                    return content;
                }
                // è®°å½•总需求数量
                totalNeedAllocate = calculationResult.MaterielCalculations.Sum(x => x.UnallocatedQuantity);
                // 2. è°ƒç”¨å‡ºåº“处理逻辑,锁定库存,生成出库记录等
                // 2. å¤„理物料分配
                List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
                // èŽ·å–å‡ºåº“å•ä¿¡æ¯
                Dt_OutboundOrder outboundOrder = calculationResult.OutboundOrder;
                // å‡ºåº“详情添加或修改集合
                List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>();
                List<Dt_OutboundOrderDetail> outboundOrderDetails = new();
                List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
                List<Dt_Task> tasks = new List<Dt_Task>();
                foreach (var materielCalc in calculationResult.MaterielCalculations)
                {
                    (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) materielPickedDetails = ProcessMaterielTaskGeneration(outboundOrder, materielCalc, request, calculationResult.FactoryArea);
                    var materielPickedDetails = ProcessMaterielTaskGeneration(outboundOrder, materielCalc, request, calculationResult.FactoryArea);
                    // è®¡ç®—当前物料实际分配量
                    decimal actualAllocatedQuantity = materielPickedDetails.PickedDetails.Sum(x => x.OutboundQuantity);
                    actualAllocatedQuantity = Math.Min(actualAllocatedQuantity, materielCalc.UnallocatedQuantity);
                    totalActualAllocate += actualAllocatedQuantity; // ç´¯åŠ è‡³æ€»å®žé™…åˆ†é…é‡
                    materielCalc.UnallocatedQuantity = materielCalc.UnallocatedQuantity - actualAllocatedQuantity;
                    // å¤„理出库锁定记录
                    foreach (var item in materielPickedDetails.OutStockLockInfo)
                    {
                        Dt_OutStockLockInfo? outStockLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0);
                        if (outStockLockInfo != null)
                        Dt_OutStockLockInfo? existLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0);
                        if (existLockInfo != null)
                        {
                            outStockLockInfo = item;
                            existLockInfo = item;
                            Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                            if (task != null)
                            {
                                outStockLockInfo.TaskNum = task.TaskNum;
                            }
                            if (task != null) existLockInfo.TaskNum = task.TaskNum;
                        }
                        else
                        {
                            Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                            if (task != null)
                            {
                                item.TaskNum = task.TaskNum;
                            }
                            if (task != null) item.TaskNum = task.TaskNum;
                            materielCalc.OutStockLockInfos.Add(item);
                        }
                        outStockLockInfos.Add(item);
                    }
                    // å¤„理任务
                    foreach (var item in materielPickedDetails.Tasks)
                    {
                        Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                        if (task == null)
                        {
                        if (tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode) == null)
                            tasks.Add(item);
                        }
                    }
                    // æ±‡æ€»åˆ†æ‹£æ˜Žç»†
                    pickedDetails.AddRange(materielPickedDetails.PickedDetails);
                    decimal allallocatedQuantity = materielCalc.UnallocatedQuantity;
                    // æ›´æ–°å‡ºåº“单明细(增加锁定数量,不增加已出数量)
                    // æŒ‰å®žé™…分配量更新单据锁定数量
                    decimal remainingToLock = actualAllocatedQuantity;
                    foreach (var detail in materielCalc.Details)
                    {
                        if (allallocatedQuantity <= 0) break;
                        decimal lockQuantity = (detail.OrderQuantity - detail.OverOutQuantity);
                        if (lockQuantity < materielCalc.UnallocatedQuantity)
                        {
                            detail.LockQuantity += lockQuantity; // å¢žåŠ é”å®šæ•°é‡ ä¸æ›´æ–° OverOutQuantity å’Œ OrderDetailStatus,因为还没有实际出库
                            outboundOrderDetails.Add(detail);
                            materielCalc.UnallocatedQuantity -= lockQuantity;
                        }
                        else
                        {
                            detail.LockQuantity += materielCalc.UnallocatedQuantity;
                            outboundOrderDetails.Add(detail);
                            break;
                        }
                        if (remainingToLock <= 0) break;
                        decimal maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity;
                        if (maxLockableQty <= 0) continue;
                        decimal currentLockQty = Math.Min(remainingToLock, maxLockableQty);
                        detail.LockQuantity += currentLockQty;
                        outboundOrderDetails.Add(detail);
                        remainingToLock -= currentLockQty;
                    }
                }
                // 3. æ›´æ–°å‡ºåº“单状态为出库中(表示已有任务分配)
                // 3. æ‰¹é‡æ›´æ–°çŠ¶æ€ï¼ˆåŽŸæœ‰é€»è¾‘ä¸å˜ï¼‰
                UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.出库中);
                // 4. æ›´æ–°å‡ºåº“单明细锁定数量
                _detailRepository.UpdateData(outboundOrderDetails);
                if (pickedDetails.Any())
                {
                    UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.出库锁定.ObjToInt());
                    UpdateLocationStatus(pickedDetails.Select(x => x.LocationCode).ToList(), LocationStatusEnum.Lock.ObjToInt());
                }
                //重检单不拣选,去掉锁定记录回库,再次组盘时扣除原条码
                if (outboundOrder.OrderType != InOrderTypeEnum.ReCheck.ObjToInt())
                {
                    UpdateOutStockLockInfo(outStockLockInfos);
                }
                if (tasks.Any()) _taskRepository.AddData(tasks);
                // 5. æ›´æ–°åº“存状态
                UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.出库锁定.ObjToInt());
                    _unitOfWorkManage.CommitTran();
                // 6. æ›´æ–°è´§ä½çŠ¶æ€
                UpdateLocationStatus(pickedDetails.Select(x => x.LocationCode).ToList(), LocationStatusEnum.Lock.ObjToInt());
                // 7. æ›´æ–°åº“存详情
                UpdateOutStockLockInfo(outStockLockInfos);
                // 8. æ·»åŠ ä»»åŠ¡æ•°æ®
                _taskRepository.AddData(tasks);
                _unitOfWorkManage.CommitTran();
                // 4. æž„造响应:区分「全部分配」和「部分分配」
                string responseMsg = totalActualAllocate == totalNeedAllocate
                    ? "分拣任务分配成功"
                    : $"分拣任务分配完成(实际分配{totalActualAllocate},需求{totalNeedAllocate},库存不足部分未分配)";
                Dt_OutboundOrder outboundOrder1 = _outboundRepository.Db.Queryable<Dt_OutboundOrder>().Where(x => x.OrderNo == request.OrderNo).Includes(x=>x.Details).First();
                if(totalActualAllocate == 0 && !outboundOrder1.Details.Any(x=>x.LockQuantity >0))
                {
                    UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.未开始);
                    return WebResponseContent.Instance.Error("分配库存数量为0,无法出库");
                }
                response.Success = true;
                response.Message = "分拣任务分配成功";
                response.Tasks = tasks; // è¿”回第一个任务号
                response.PickedDetails = pickedDetails; // è¿”回第一个分拣明细
                content = WebResponseContent.Instance.OK("分拣任务分配成功", response);
                return content;
                response.Message = responseMsg;
                response.Tasks = tasks;
                response.PickedDetails = pickedDetails;
                content = WebResponseContent.Instance.OK(responseMsg, response);
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                content = WebResponseContent.Instance.Error("处理拣货出库失败:" + ex.Message);
            }
            return content;
        }
        /// <summary>
        /// è®¡ç®—出库数量逻辑
        /// è®¡ç®—出库数量逻辑(原有逻辑不变)
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public OutboundCalculationDTO CalcOutboundQuantity(PickingOutboundRequestDTO request)
        {
            OutboundCalculationDTO result = new();
            OutboundCalculationDTO result = new OutboundCalculationDTO();
            try
            {
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
@@ -249,15 +278,15 @@
                }
                result.FactoryArea = outboundOrder.FactoryArea;
                // èŽ·å–é€‰æ‹©çš„å‡ºåº“æ˜Žç»†
                List<Dt_OutboundOrderDetail> selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && request.DetailIds.Contains(x.Id));
                if (outboundOrder.IsBatch == 1 && request.DetailIds.Count == 1)
                List<Dt_OutboundOrderDetail> selectedDetails = new List<Dt_OutboundOrderDetail>();
                if (request.DetailIds == null || !request.DetailIds.Any())
                {
                    selectedDetails = _detailRepository.QueryData(x => x.OrderId == selectedDetails.First().OrderId && x.WarehouseCode == selectedDetails.First().WarehouseCode && x.MaterielCode == selectedDetails.First().MaterielCode && x.BatchNo == selectedDetails.First().BatchNo && x.SupplyCode == selectedDetails.First().SupplyCode);
                    selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id);
                }
                else
                {
                    selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && request.DetailIds.Contains(x.Id));
                }
                if (!selectedDetails.Any())
                {
@@ -268,23 +297,21 @@
                if (selectedDetails.Any(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty))
                {
                    List<int> selectDetailIds = selectedDetails.Where(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty).Select(x => x.Id).ToList();
                    result.CanOutbound = false;
                    result.ErrorMessage = $"出库明细信息{string.Join(",", selectDetailIds)}已分配完成";
                    return result;
                }
                outboundOrder.Details = selectedDetails;
                result.OutboundOrder = outboundOrder;
                result.SelectedDetails = selectedDetails;
                if (outboundOrder.IsBatch == 0 || request.DetailIds.Count > 1)
                if (outboundOrder.IsBatch == 0 || request.DetailIds.Count != 1)
                {
                    // å¤šæ˜Žç»†å‡ºåº“:按物料分组处理
                    result.MaterielCalculations = CalcMaterielOutboundQuantities(outboundOrder, selectedDetails.ToList());
                }
                else
                {
                    // å•明细出库:验证输入的出库数量
                    if (!request.OutboundQuantity.HasValue || request.OutboundQuantity.Value <= 0)
                    {
                        result.CanOutbound = false;
@@ -296,10 +323,8 @@
                    decimal orderQuantity = selectedDetails.Sum(x => x.OrderQuantity);
                    decimal moveQuantity = selectedDetails.Sum(x => x.MoveQty);
                    decimal overQuantity = selectedDetails.Sum(x => x.OverOutQuantity);
                    Dt_OutboundOrderDetail? singleDetail = selectedDetails.First();
                    //判断可出库数量
                    if (orderQuantity - lockQuantity - moveQuantity < request.OutboundQuantity.Value || orderQuantity - overQuantity - moveQuantity < request.OutboundQuantity.Value)
                    {
                        result.CanOutbound = false;
@@ -313,10 +338,7 @@
                    {
                        inputQuantity -= (item.OrderQuantity - item.MoveQty - item.LockQuantity);
                        outboundOrderDetails.Add(item);
                        if (inputQuantity <= 0)
                        {
                            break;
                        }
                        if (inputQuantity <= 0) break;
                    }
                    result.MaterielCalculations = new List<MaterielOutboundCalculationDTO>()
@@ -336,35 +358,27 @@
                            Details = outboundOrderDetails
                        }
                    };
                    outboundOrder.Details = outboundOrderDetails;
                }
                result.CanOutbound = true;
                return result;
            }
            catch (Exception ex)
            {
                result.CanOutbound = false;
                result.ErrorMessage = ex.Message;
                return result;
            }
            return result;
        }
        /// <summary>
        /// å¤šå‡ºåº“单明细时,按物料分组计算出库数量
        /// æŒ‰ç‰©æ–™åˆ†ç»„计算出库数量(原有逻辑不变)
        /// </summary>
        /// <param name="selectedDetails"></param>
        /// <returns></returns>
        private List<MaterielOutboundCalculationDTO> CalcMaterielOutboundQuantities(Dt_OutboundOrder outboundOrder, List<Dt_OutboundOrderDetail> selectedDetails)
        {
            // æŒ‰ç‰©æ–™åˆ†ç»„:物料编号、批次号、供应商编号、仓库编号
            List<MaterielOutboundCalculationDTO> materielGroups = selectedDetails
            return selectedDetails
                .GroupBy(x => new
                {
                    x.MaterielCode,
                    x.MaterielName,
                    x.BatchNo,
                    x.SupplyCode,
                    x.WarehouseCode
@@ -372,7 +386,6 @@
                .Select(g => new MaterielOutboundCalculationDTO
                {
                    MaterielCode = g.Key.MaterielCode,
                    MaterielName = g.Key.MaterielName,
                    BatchNo = g.Key.BatchNo,
                    SupplyCode = g.Key.SupplyCode,
                    WarehouseCode = g.Key.WarehouseCode,
@@ -382,138 +395,395 @@
                    UnallocatedQuantity = g.Sum(x => x.OrderQuantity - x.LockQuantity - x.MoveQty),
                    MovedQuantity = g.Sum(x => x.MoveQty),
                    Details = g.ToList(),
                    OutStockLockInfos = _outboundLockInfoRepository.QueryData(x => x.MaterielCode == g.Key.MaterielCode && x.BatchNo == g.Key.BatchNo && x.OrderType == (int)outboundOrder.OrderType && x.OrderNo == outboundOrder.OrderNo)
                    OutStockLockInfos = _outboundLockInfoRepository.QueryData(x =>
                        x.MaterielCode == g.Key.MaterielCode &&
                        x.BatchNo == g.Key.BatchNo &&
                        x.OrderType == (int)outboundOrder.OrderType &&
                        x.OrderNo == outboundOrder.OrderNo)
                })
                .ToList();
            return materielGroups;
        }
        /// <summary>
        /// å¤„理物料的任务生成
        /// å¤„理物料任务生成(核心:有多少分多少+移除库存不足异常)
        /// </summary>
        /// <param name="outboundOrder">出库订单</param>
        /// <param name="materielCalc">按物料的出库计算结果</param>
        /// <param name="request">分拣出库请求</param>
        /// <param name="factoryArea"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) ProcessMaterielTaskGeneration(Dt_OutboundOrder outboundOrder, MaterielOutboundCalculationDTO materielCalc, PickingOutboundRequestDTO request, string factoryArea)
        private (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) ProcessMaterielTaskGeneration(
    Dt_OutboundOrder outboundOrder,
    MaterielOutboundCalculationDTO materielCalc,
    PickingOutboundRequestDTO request,
    string factoryArea)
        {
            List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
            List<Dt_Task> generatedTasks = new List<Dt_Task>();
            // æž„建库存查询条件(包含库存表、库存明细)
            List<Dt_StockInfo> stockQuery = BuildStockQueryWithInfo(materielCalc, factoryArea);
            if (!stockQuery.Any())
            {
                throw new Exception($"物料 {materielCalc.MaterielCode} å¯¹åº”的库存不存在");
            }
            // æ‰¹é‡è®¡ç®—总可用库存数量
            (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) data = GetBatchAvailableStockQuantities(materielCalc, stockQuery);
            // å¯ç”¨åº“存数量映射
            Dictionary<int, decimal> availableStockMap = data.AvailableStockMap;
            // ç‰©æ–™æ€»å¯ç”¨åº“存数量
            decimal totalAvailableStock = availableStockMap.Values.Sum();
            // å·²é”å®šåº“存数量映射
            Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = data.LockStockMap;
            // éªŒè¯æ€»å¯ç”¨åº“存是否满足出库需求
            if (totalAvailableStock < materielCalc.UnallocatedQuantity)
            {
                throw new Exception($"物料 {materielCalc.MaterielCode} å¯ç”¨åº“å­˜ {totalAvailableStock} ä¸è¶³å‡ºåº“数量 {materielCalc.UnallocatedQuantity}");
            }
            //decimal remainingQuantity = Math.Min(totalAvailableStock, materielCalc.UnallocatedQuantity);
            // éœ€åˆ†é…æ•°é‡
            List<Dt_Task> generatedTasks = new List<Dt_Task>(); // åˆå§‹ç©ºä»»åŠ¡åˆ—è¡¨
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            decimal remainingQuantity = materielCalc.UnallocatedQuantity;
            // å·²åˆ†é…çš„æ‰˜ç›˜åˆ—表
            List<string> allocatedPallets = new List<string>();
            // èŽ·å–ç¬¬ä¸€ä¸ªå‡ºåº“æ˜Žç»†
            Dt_OutboundOrderDetail firstDetail = materielCalc.Details.First();
            // é¢„加载库存明细和锁定记录
            List<int> stockIds = stockQuery.Select(x => x.Id).ToList();
            Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = stockQuery.ToDictionary(x => x.Id, x => x.Details);
            // è®°å½•每个托盘的实际分配量
            Dictionary<string, decimal> palletAllocations = new Dictionary<string, decimal>();
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            foreach (var stock in stockQuery)
            // 1. ä¼˜å…ˆå¤„理指定库存明细
            if (request.StockDetailIds?.Any() == true)
            {
                if (remainingQuantity <= 0) break;
                var specifiedResult = AllocateSpecifiedStockDetails(outboundOrder, materielCalc, request, factoryArea, remainingQuantity);
                pickedDetails.AddRange(specifiedResult.PickedDetails);
                lockInfoList.AddRange(specifiedResult.LockInfoList);
                remainingQuantity -= specifiedResult.ActualAllocatedQuantity;
                // å½“前库存可用数量
                decimal availableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0);
                if (availableQuantity <= 0) continue;
                // è®¡ç®—该托盘可分配数量
                decimal allocateQuantity = Math.Min(remainingQuantity, availableQuantity);
                (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) actualAllocated = AllocateStockQuantity(stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail, request, lockStockMap.GetValueOrDefault(stock.Id, new List<Dt_OutStockLockInfo>()), stockDetailMap);
                // æœ¬æ¬¡åˆ†é…çš„æ•°é‡
                decimal actualAllocatedQuantity = actualAllocated.ActualAllocatedQuantity;
                if (actualAllocatedQuantity > 0)
                // ===== æ ¸å¿ƒä¿®å¤ï¼šæŒ‡å®šåº“存分配后,立即生成任务(无论剩余量多少)=====
                if (specifiedResult.PickedDetails.Any()) // æœ‰æŒ‡å®šåº“存分配结果就生成任务
                {
                    allocatedPallets.Add(stock.PalletCode);
                    palletAllocations[stock.PalletCode] = actualAllocatedQuantity; // è®°å½•实际分配量
                    remainingQuantity -= actualAllocatedQuantity;
                    lockInfoList.AddRange(actualAllocated.LockInfoList);
                    var specifiedTasks = GenerateTasksForSpecifiedStock(specifiedResult.PickedDetails, request.OutboundTargetLocation);
                    generatedTasks.AddRange(specifiedTasks); // æ·»åŠ æŒ‡å®šåº“å­˜ä»»åŠ¡åˆ°æ€»ä»»åŠ¡åˆ—è¡¨
                }
                // å‰©ä½™é‡<=0时,直接返回(已生成指定库存任务)
                if (remainingQuantity <= 0)
                {
                    return (pickedDetails, generatedTasks, lockInfoList);
                }
            }
            foreach (var palletCode in allocatedPallets)
            // 2. å¤„理剩余数量的自动分配(原有逻辑不变,自动分配的任务会追加到generatedTasks)
            List<Dt_StockInfo> stockQuery = BuildStockQueryWithInfo(materielCalc, factoryArea);
            var allocatedPalletCodes = pickedDetails.Select(x => x.PalletCode).Distinct().ToList();
            stockQuery = stockQuery.Where(x => !allocatedPalletCodes.Contains(x.PalletCode)).ToList();
            var stockData = GetBatchAvailableStockQuantities(materielCalc, stockQuery);
            decimal totalAutoAvailable = stockData.AvailableStockMap.Values.Sum();
            decimal autoAllocateQuantity = Math.Min(remainingQuantity, totalAutoAvailable);
            remainingQuantity -= autoAllocateQuantity;
            if (autoAllocateQuantity > 0 && stockQuery.Any())
            {
                Dt_StockInfo stock = stockQuery.First(x => x.PalletCode == palletCode);
                Dt_OutboundOrderDetail firstDetail = materielCalc.Details.First();
                Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = stockQuery.ToDictionary(x => x.Id, x => x.Details);
                Dictionary<string, decimal> palletAllocations = new Dictionary<string, decimal>();
                // èŽ·å–å®žé™…åˆ†é…çš„æ•°é‡
                decimal actualAllocatedQuantity = palletAllocations.GetValueOrDefault(palletCode, 0);
                // è®¡ç®—分配后剩余的库存数量
                decimal originalAvailableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0);
                decimal remainingStockQuantity = Math.Max(0, originalAvailableQuantity - actualAllocatedQuantity);
                pickedDetails.Add(new PickedStockDetailDTO
                foreach (var stock in stockQuery)
                {
                    PalletCode = palletCode,
                    MaterielCode = materielCalc.MaterielCode,
                    OutboundQuantity = actualAllocatedQuantity, // æœ¬æ¬¡å®žé™…分配的出库量
                    RemainingQuantity = remainingStockQuantity, // åˆ†é…åŽå‰©ä½™çš„可用库存
                    LocationCode = stock.LocationCode,
                    OutStockLockInfos = lockInfoList
                });
                    if (autoAllocateQuantity <= 0) break;
                    decimal availableQuantity = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0);
                    if (availableQuantity <= 0) continue;
                Dt_OutStockLockInfo? outStockLockInfo = lockInfoList.FirstOrDefault(x => x.PalletCode == palletCode);
                int taskNum = outStockLockInfo?.TaskNum ?? Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt();
                    // è‡ªåŠ¨åˆ†é…æ—¶æŸ¥è¯¢æ‰˜ç›˜æ€»åº“å­˜ï¼ˆåŽŸæœ‰ä¼˜åŒ–é€»è¾‘ä¸å˜ï¼‰
                    decimal palletMaterielTotalStock = _stockDetailRepository.QueryData(
                        x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode
                    ).Sum(x => x.StockQuantity);
                Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, request.OutboundTargetLocation);
                if (generatedTasks.FirstOrDefault(x => x.PalletCode == stock.PalletCode) == null) generatedTasks.Add(task);
                    decimal allocateQuantity = Math.Min(autoAllocateQuantity, availableQuantity);
                    var actualAllocated = AllocateStockQuantity(
                        stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail,
                        request, stockData.LockStockMap.GetValueOrDefault(stock.Id, new List<Dt_OutStockLockInfo>()),
                        stockDetailMap, palletMaterielTotalStock);
                    if (actualAllocated.ActualAllocatedQuantity > 0)
                    {
                        palletAllocations[stock.PalletCode] = actualAllocated.ActualAllocatedQuantity;
                        autoAllocateQuantity -= actualAllocated.ActualAllocatedQuantity;
                        lockInfoList.AddRange(actualAllocated.LockInfoList);
                    }
                }
                // ç”Ÿæˆè‡ªåŠ¨åˆ†é…çš„ä»»åŠ¡ï¼ˆè¿½åŠ åˆ°generatedTasks,不会重复)
                foreach (var palletCode in palletAllocations.Keys)
                {
                    Dt_StockInfo stock = stockQuery.First(x => x.PalletCode == palletCode);
                    decimal actualQty = palletAllocations[palletCode];
                    decimal originalAvailable = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0);
                    pickedDetails.Add(new PickedStockDetailDTO
                    {
                        PalletCode = palletCode,
                        MaterielCode = materielCalc.MaterielCode,
                        OutboundQuantity = actualQty,
                        RemainingQuantity = Math.Max(0, originalAvailable - actualQty),
                        LocationCode = stock.LocationCode,
                        OutStockLockInfos = lockInfoList.Where(x => x.PalletCode == palletCode).ToList()
                    });
                    int taskNum = lockInfoList.FirstOrDefault(x => x.PalletCode == palletCode)?.TaskNum ??
                                  Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt();
                    Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, request.OutboundTargetLocation);
                    // è¿‡æ»¤é‡å¤ä»»åŠ¡ï¼ˆæŒ‡å®šåº“å­˜çš„æ‰˜ç›˜å·²ç”Ÿæˆï¼Œä¸ä¼šé‡å¤æ·»åŠ ï¼‰
                    if (generatedTasks.FirstOrDefault(x => x.PalletCode == palletCode) == null)
                        generatedTasks.Add(task);
                }
            }
            // è¿”回「指定库存任务+自动分配任务」的合并列表
            return (pickedDetails, generatedTasks, lockInfoList);
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务
        /// åˆ†é…æŒ‡å®šåº“存明细(核心优化:查询托盘物料总库存并传递)
        /// </summary>
        /// <param name="stockInfo"></param>
        /// <param name="taskType"></param>
        /// <param name="outStation"></param>
        /// <returns></returns>
        private (decimal ActualAllocatedQuantity, List<PickedStockDetailDTO> PickedDetails, List<Dt_OutStockLockInfo> LockInfoList) AllocateSpecifiedStockDetails(
            Dt_OutboundOrder outboundOrder,
            MaterielOutboundCalculationDTO materielCalc,
            PickingOutboundRequestDTO request,
            string factoryArea,
            decimal needAllocateQuantity)
        {
            List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            decimal actualAllocated = 0;
            List<Dt_StockInfoDetail> specifiedStockDetails = _stockDetailRepository.QueryData(
                x => request.StockDetailIds.Contains(x.Id)
                && x.MaterielCode == materielCalc.MaterielCode
                && x.StockQuantity > 0
                && (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动冻结 || x.Status == (int)StockStatusEmun.手动解锁 || x.Status == (int)StockStatusEmun.过期));
            if (!specifiedStockDetails.Any())
            {
                throw new Exception($"指定库存明细ID [{string.Join(",", request.StockDetailIds)}] ä¸å­˜åœ¨æˆ–不可用");
            }
            List<int> stockIds = specifiedStockDetails.Select(x => x.StockId).Distinct().ToList();
            List<Dt_StockInfo> specifiedStocks = _stockInfoRepository.QueryData(x => stockIds.Contains(x.Id));
            Dictionary<int, Dt_StockInfo> stockMap = specifiedStocks.ToDictionary(x => x.Id);
            foreach (var stockDetail in specifiedStockDetails)
            {
                if (needAllocateQuantity <= 0) break;
                if (!stockMap.ContainsKey(stockDetail.StockId)) continue;
                Dt_StockInfo stock = stockMap[stockDetail.StockId];
                decimal availableQty = stockDetail.StockQuantity;
                decimal allocateQty = Math.Min(needAllocateQuantity, availableQty);
                if (allocateQty <= 0) continue;
                // ===== æ ¸å¿ƒä¼˜åŒ–1:查询该托盘下当前物料的总库存 =====
                decimal palletMaterielTotalStock = _stockDetailRepository.QueryData(
                    x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode
                ).Sum(x => x.StockQuantity); // è¯¥æ‰˜ç›˜è¯¥ç‰©æ–™çš„æ€»åº“存(而非本次分配量)
                var lockInfos = materielCalc.OutStockLockInfos
                    .Where(x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode)
                    .ToList();
                // ===== ä¼ é€’总库存到AllocateStockQuantity =====
                var allocateResult = AllocateStockQuantity(
                    stock, allocateQty, availableQty, outboundOrder, materielCalc.Details.First(),
                    request, lockInfos,
                    new Dictionary<int, List<Dt_StockInfoDetail>> { { stock.Id, new List<Dt_StockInfoDetail> { stockDetail } } },
                    palletMaterielTotalStock // æ–°å¢žï¼šä¼ å…¥æ‰˜ç›˜ç‰©æ–™æ€»åº“å­˜
                );
                if (allocateResult.ActualAllocatedQuantity > 0)
                {
                    pickedDetails.Add(new PickedStockDetailDTO
                    {
                        PalletCode = stock.PalletCode,
                        MaterielCode = materielCalc.MaterielCode,
                        OutboundQuantity = allocateResult.ActualAllocatedQuantity,
                        RemainingQuantity = Math.Max(0, availableQty - allocateResult.ActualAllocatedQuantity),
                        LocationCode = stock.LocationCode,
                        OutStockLockInfos = allocateResult.LockInfoList
                    });
                    actualAllocated += allocateResult.ActualAllocatedQuantity;
                    needAllocateQuantity -= allocateResult.ActualAllocatedQuantity;
                    lockInfoList.AddRange(allocateResult.LockInfoList);
                }
            }
            return (actualAllocated, pickedDetails, lockInfoList);
        }
        /// <summary>
        /// ä¸ºæŒ‡å®šåº“存生成任务(原有逻辑不变)
        /// </summary>
        private List<Dt_Task> GenerateTasksForSpecifiedStock(List<PickedStockDetailDTO> pickedDetails, string outboundTargetLocation)
        {
            List<Dt_Task> tasks = new List<Dt_Task>();
            var palletCodes = pickedDetails.Select(x => x.PalletCode).Distinct().ToList();
            foreach (var palletCode in palletCodes)
            {
                Dt_StockInfo stock = _stockInfoRepository.QueryFirst(x => x.PalletCode == palletCode);
                if (stock == null) continue;
                int taskNum = pickedDetails.First(x => x.PalletCode == palletCode)
                    .OutStockLockInfos.FirstOrDefault()?.TaskNum ??
                    Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt();
                Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, outboundTargetLocation);
                if (tasks.FirstOrDefault(x => x.PalletCode == palletCode) == null)
                    tasks.Add(task);
            }
            return tasks;
        }
        /// <summary>
        /// æž„建库存查询条件(原有逻辑不变)
        /// </summary>
        private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea)
        {
            ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0
                && (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动解锁));
            if (!string.IsNullOrEmpty(materielCalc.SupplyCode))
                stockDetails = stockDetails.Where(x => x.SupplyCode == materielCalc.SupplyCode);
            if (!string.IsNullOrEmpty(materielCalc.WarehouseCode))
                stockDetails = stockDetails.Where(x => x.WarehouseCode == materielCalc.WarehouseCode);
            if (!string.IsNullOrEmpty(factoryArea))
                stockDetails = stockDetails.Where(x => x.FactoryArea == factoryArea);
            if (!string.IsNullOrEmpty(materielCalc.BatchNo))
                stockDetails = stockDetails.Where(x => x.BatchNo == materielCalc.BatchNo);
            List<Dt_StockInfoDetail> stockDetailList = stockDetails.ToList();
            List<string> locationCodes = _locationInfoRepository.QueryData(x =>
                (x.LocationStatus == LocationStatusEnum.InStock.ObjToInt() || x.LocationStatus == LocationStatusEnum.Lock.ObjToInt())
                && x.EnableStatus == EnableStatusEnum.Normal.ObjToInt()).Select(x => x.LocationCode).ToList();
            List<int> stockIds = stockDetailList.GroupBy(x => x.StockId).Select(x => x.Key).ToList();
            List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x =>
                stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.入库完成.ObjToInt())
                && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode));
            foreach (var stockInfo in stockInfos)
            {
                stockInfo.Details = stockDetailList.Where(x => x.StockId == stockInfo.Id).ToList();
            }
            return stockInfos;
        }
        /// <summary>
        /// æ‰¹é‡èŽ·å–æ‰˜ç›˜å¯ç”¨åº“å­˜ä¿¡æ¯ï¼ˆåŽŸæœ‰é€»è¾‘ä¸å˜ï¼‰
        /// </summary>
        private (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) GetBatchAvailableStockQuantities(
            MaterielOutboundCalculationDTO materielCalc, List<Dt_StockInfo> stockInfos)
        {
            List<int> stockIds = stockInfos.Select(x => x.Id).ToList();
            Dictionary<int, decimal> availableStockMap = new Dictionary<int, decimal>();
            Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = new Dictionary<int, List<Dt_OutStockLockInfo>>();
            List<Dt_OutStockLockInfo> allocatedData = materielCalc.OutStockLockInfos
                .Where(x => stockIds.Contains(x.StockId) && x.MaterielCode == materielCalc.MaterielCode).ToList();
            foreach (var stockInfo in stockInfos)
            {
                decimal totalQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
                List<Dt_OutStockLockInfo> outStockLockInfos = allocatedData
                    .Where(x => x.StockId == stockInfo.Id && x.MaterielCode == materielCalc.MaterielCode).ToList();
                decimal allocatedQuantity = outStockLockInfos.Sum(x => x.AllocatedQuantity);
                availableStockMap[stockInfo.Id] = Math.Max(0, totalQuantity - allocatedQuantity);
                lockStockMap[stockInfo.Id] = outStockLockInfos;
            }
            return (availableStockMap, lockStockMap);
        }
        /// <summary>
        /// åˆ†é…åº“存(核心优化:OriginalQuantity赋值为托盘物料总库存)
        /// </summary>
        private (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) AllocateStockQuantity(
            Dt_StockInfo stockInfo, decimal allocateQuantity, decimal availableQuantity,
            Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail detail,
            PickingOutboundRequestDTO request, List<Dt_OutStockLockInfo> lockInfos,
            Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = null,
            decimal palletMaterielTotalStock = 0 // æ–°å¢žï¼šæ‰˜ç›˜ç‰©æ–™æ€»åº“存参数
        )
        {
            decimal actualAllocatedQuantity = Math.Min(allocateQuantity, availableQuantity);
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            if (actualAllocatedQuantity > 0)
            {
                // æ£€æŸ¥ç›®æ ‡ä½ç½®ä¸€è‡´æ€§
                if (lockInfos.Any() && !string.IsNullOrEmpty(lockInfos.First().OutboundTargetLocation)
                    && !string.Equals(lockInfos.First().OutboundTargetLocation, request.OutboundTargetLocation, StringComparison.OrdinalIgnoreCase))
                {
                    return (0, lockInfoList);
                }
                Dt_OutStockLockInfo? lockInfo = lockInfos.FirstOrDefault(x =>
                    x.StockId == stockInfo.Id && x.Status == OutLockStockStatusEnum.已分配.ObjToInt()
                    && x.PalletCode == stockInfo.PalletCode && x.OrderNo == outboundOrder.OrderNo);
                if (lockInfo != null)
                {
                    List<string> currentIds = lockInfo.OrderDetailIds?.Split(',').ToList() ?? new List<string>();
                    if (!currentIds.Contains(detail.Id.ToString()))
                    {
                        currentIds.Add(detail.Id.ToString());
                        lockInfo.OrderDetailIds = string.Join(",", currentIds);
                    }
                    decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
                    lockInfo.AssignQuantity += actualAllocatedQuantity;
                    lockInfo.AllocatedQuantity = totalAllocatedQuantity;
                    if (palletMaterielTotalStock > 0)
                        lockInfo.OriginalQuantity = palletMaterielTotalStock;
                    lockInfoList.Add(lockInfo);
                }
                else
                {
                    decimal originalQuantity = palletMaterielTotalStock;
                    List<string> allDetailIds = outboundOrder.Details.Where(x =>
                        x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode
                        && x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode
                        && x.WarehouseCode == detail.WarehouseCode)
                        .Select(x => x.Id.ToString()).ToList();
                    decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
                    lockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = request.OrderNo,
                        OrderDetailIds = string.Join(",", allDetailIds),
                        OrderType = outboundOrder.OrderType,
                        BatchNo = detail.BatchNo,
                        MaterielCode = detail.MaterielCode,
                        MaterielName = detail.MaterielName,
                        StockId = stockInfo.Id,
                        OrderQuantity = allDetailIds.Count > 1
                            ? outboundOrder.Details.Where(x =>
                                x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode
                                && x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode
                                && x.WarehouseCode == detail.WarehouseCode).Sum(x => x.OrderQuantity)
                            : detail.OrderQuantity,
                        OriginalQuantity = originalQuantity, // çŽ°åœ¨èµ‹å€¼ä¸ºæ‰˜ç›˜ç‰©æ–™æ€»åº“å­˜
                        AssignQuantity = actualAllocatedQuantity,
                        AllocatedQuantity = totalAllocatedQuantity,
                        LocationCode = stockInfo.LocationCode,
                        PalletCode = stockInfo.PalletCode,
                        Unit = detail.Unit,
                        OutboundTargetLocation = request.OutboundTargetLocation,
                        Status = OutLockStockStatusEnum.已分配.ObjToInt(),
                        SupplyCode = detail.SupplyCode,
                        WarehouseCode = detail.WarehouseCode,
                        FactoryArea = outboundOrder.FactoryArea,
                        TaskNum = Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(),
                        OrderDetailId = 0
                    };
                    lockInfoList.Add(lockInfo);
                }
            }
            return (actualAllocatedQuantity, lockInfoList);
        }
        /// <summary>
        /// è®¡ç®—该托盘累计已分配数量(原有逻辑不变)
        /// </summary>
        private decimal CalcTotalAllocatedQuantity(List<Dt_OutStockLockInfo> lockInfos, int stockId, string materielCode)
        {
            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
                x.StockId == stockId && x.MaterielCode == materielCode);
            return lockRecords?.Sum(x => x.AssignQuantity) ?? 0;
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务(原有逻辑不变)
        /// </summary>
        public Dt_Task GenerationOutTask(Dt_StockInfo stockInfo, TaskTypeEnum taskType, int taskNum, string outStation)
        {
            Dt_Task task = new()
            return new Dt_Task
            {
                CurrentAddress = stockInfo.LocationCode,
                Grade = 0,
@@ -528,241 +798,18 @@
                PalletType = stockInfo.PalletType,
                WarehouseId = stockInfo.WarehouseId,
            };
            return task;
        }
        /// <summary>
        /// æž„建库存查询条件(包含库存信息和库存明细)
        /// </summary>
        /// <param name="materielCalc"></param>
        /// <param name="factoryArea"></param>
        /// <returns></returns>
        private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea)
        {
            // åŸºç¡€æŸ¥è¯¢æ¡ä»¶ï¼šç‰©æ–™ç¼–号、批次号(如果提供)、库存数量>0
            ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0);
            // æ ¹æ®æ¡ä»¶æ·»åŠ ä¾›åº”å•†ç¼–å·åŒ¹é…ï¼ˆä¸ä¸ºç©ºæ—¶æ‰éœ€è¦åŒ¹é…ï¼‰
            if (!string.IsNullOrEmpty(materielCalc.SupplyCode))
            {
                stockDetails = stockDetails.Where(x => x.SupplyCode == materielCalc.SupplyCode);
            }
            // æ ¹æ®æ¡ä»¶æ·»åŠ ä»“åº“ç¼–å·åŒ¹é…ï¼ˆä¸ä¸ºç©ºæ—¶æ‰éœ€è¦åŒ¹é…ï¼‰
            if (!string.IsNullOrEmpty(materielCalc.WarehouseCode))
            {
                stockDetails = stockDetails.Where(x => x.WarehouseCode == materielCalc.WarehouseCode);
            }
            // æ ¹æ®æ¡ä»¶æ·»åŠ åŽ‚åŒºåŒ¹é…ï¼ˆä¸ä¸ºç©ºæ—¶æ‰éœ€è¦åŒ¹é…ï¼‰
            if (!string.IsNullOrEmpty(factoryArea))
            {
                stockDetails = stockDetails.Where(x => x.FactoryArea == factoryArea);
            }
            // æ ¹æ®æ‰¹æ¬¡å·è¿›è¡Œè¿‡æ»¤ï¼ˆå¦‚果提供)
            if (!string.IsNullOrEmpty(materielCalc.BatchNo))
            {
                stockDetails = stockDetails.Where(x => x.BatchNo == materielCalc.BatchNo);
            }
            List<Dt_StockInfoDetail> stockDetailList = stockDetails.ToList();
            // èŽ·å–å¯ç”¨è´§ä½ç¼–å·
            List<string> locationCodes = _locationInfoRepository.QueryData(x => (x.LocationStatus == LocationStatusEnum.InStock.ObjToInt() /*|| x.LocationStatus == LocationStatusEnum.Lock.ObjToInt()*/) && x.EnableStatus == EnableStatusEnum.Normal.ObjToInt()).Select(x => x.LocationCode).ToList();
            // èŽ·å–æ‰€æœ‰ç›¸å…³çš„åº“å­˜ä¿¡æ¯
            List<int> stockIds = stockDetailList.GroupBy(x => x.StockId).Select(x => x.Key).ToList();
            List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x => stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.入库完成.ObjToInt() /*|| x.StockStatus == StockStatusEmun.出库锁定.ObjToInt()*/) && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode));
            // åœ¨å†…存中关联数据
            foreach (var stockInfo in stockInfos)
            {
                stockInfo.Details = new List<Dt_StockInfoDetail>();
                stockInfo.Details = stockDetailList.Where(x => x.StockId == stockInfo.Id).ToList();
            }
            return stockInfos;
        }
        /// <summary>
        /// æ‰¹é‡èŽ·å–æ‰˜ç›˜å¯ç”¨åº“å­˜ä¿¡æ¯
        /// </summary>
        /// <param name="stockInfos">库存信息列表</param>
        /// <param name="materielCode">物料编号</param>
        /// <returns>返回值为(库存主键,可用数量)字典</returns>
        private (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) GetBatchAvailableStockQuantities(MaterielOutboundCalculationDTO materielCalc, List<Dt_StockInfo> stockInfos)
        {
            List<int> stockIds = stockInfos.Select(x => x.Id).ToList();
            Dictionary<int, decimal> availableStockMap = new Dictionary<int, decimal>(); // å¯ç”¨åº“存数量
            Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = new Dictionary<int, List<Dt_OutStockLockInfo>>(); // å·²é”å®šåº“存数量
            // æ‰¹é‡æŸ¥è¯¢å·²åˆ†é…æ•°é‡
            List<Dt_OutStockLockInfo> allocatedData = materielCalc.OutStockLockInfos.Where(x => stockIds.Contains(x.StockId) && x.MaterielCode == materielCalc.MaterielCode).ToList();
            foreach (var stockInfo in stockInfos)
            {
                // è®¡ç®—总库存数量
                decimal totalQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
                List<Dt_OutStockLockInfo> outStockLockInfos = allocatedData.Where(x => x.StockId == stockInfo.Id && x.MaterielCode == materielCalc.MaterielCode).ToList();
                // è®¡ç®—已分配数量
                decimal allocatedQuantity = outStockLockInfos.Sum(x => x.AllocatedQuantity);
                availableStockMap[stockInfo.Id] = Math.Max(0, totalQuantity - allocatedQuantity);
                lockStockMap[stockInfo.Id] = outStockLockInfos;
            }
            return (availableStockMap, lockStockMap);
        }
        /// <summary>
        /// åˆ†é…åº“å­˜
        /// </summary>
        /// <param name="stockInfo">库存信息</param>
        /// <param name="allocateQuantity">要分配的数量</param>
        /// <param name="availableQuantity">可分配的数量</param>
        /// <param name="outboundOrder">出库单</param>
        /// <param name="detail">出库单明细</param>
        /// <param name="request"></param>
        /// <param name="lockInfos"></param>
        /// <param name="stockDetailMap"></param>
        /// <returns></returns>
        private (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) AllocateStockQuantity(Dt_StockInfo stockInfo, decimal allocateQuantity, decimal availableQuantity, Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail detail, PickingOutboundRequestDTO request, List<Dt_OutStockLockInfo> lockInfos, Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = null)
        {
            decimal actualAllocatedQuantity = Math.Min(allocateQuantity, availableQuantity); // å®žé™…分配数量
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            if (actualAllocatedQuantity > 0)
            {
                //检查目标位置一致性:如果托盘已有锁定记录且目标位置不同,则不允许分配
                if (lockInfos.Any() && !string.IsNullOrEmpty(lockInfos.First().OutboundTargetLocation))
                {
                    if (!string.Equals(lockInfos.First().OutboundTargetLocation, request.OutboundTargetLocation, StringComparison.OrdinalIgnoreCase))
                    {
                        // æ‰˜ç›˜çš„目标位置与新请求的目标位置不同,不允许使用该托盘
                        return (0, lockInfoList);
                    }
                }
                Dt_OutStockLockInfo? lockInfo = lockInfos.FirstOrDefault(x => x.StockId == stockInfo.Id && x.Status == OutLockStockStatusEnum.已分配.ObjToInt() && x.PalletCode == stockInfo.PalletCode && x.OrderNo == outboundOrder.OrderNo);
                if (lockInfo != null)
                {
                    // è¿½åŠ å½“å‰æ˜Žç»†ID到OrderDetailIds字段(避免重复)
                    List<string> currentIds = lockInfo.OrderDetailIds?.Split(',').ToList() ?? new List<string>();
                    if (!currentIds.Contains(detail.Id.ToString()))
                    {
                        currentIds.Add(detail.Id.ToString());
                        lockInfo.OrderDetailIds = string.Join(",", currentIds);
                    }
                    // è®¡ç®—该托盘该物料的总累计已出库数量(跨所有单据)
                    decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
                    // æ›´æ–°åˆ†é…å‡ºåº“量
                    decimal beforeAssignQuantity = totalAllocatedQuantity; // æœ¬æ¬¡åˆ†é…å‰çš„æ€»ç´¯è®¡é‡
                    lockInfo.AssignQuantity += actualAllocatedQuantity; // æœ¬æ¬¡åˆ†é…æ•°é‡
                    lockInfo.AllocatedQuantity = beforeAssignQuantity; // è®°å½•本次分配前的总累计量
                    lockInfoList.Add(lockInfo);
                }
                else
                {
                    // åˆ›å»ºæ–°çš„锁定记录(使用预加载的库存明细)
                    decimal originalQuantity = 0;
                    if (stockDetailMap?.ContainsKey(stockInfo.Id) == true)
                    {
                        originalQuantity = stockDetailMap[stockInfo.Id].Sum(x => x.StockQuantity);
                    }
                    // èŽ·å–è¯¥ç‰©æ–™åœ¨è¯¥è®¢å•ä¸­çš„æ‰€æœ‰æ˜Žç»†ID
                    List<string> allDetailIds = (outboundOrder.Details.Where(x =>
                        x.OrderId == outboundOrder.Id &&
                        x.MaterielCode == detail.MaterielCode &&
                        x.BatchNo == detail.BatchNo &&
                        x.SupplyCode == detail.SupplyCode &&
                        x.WarehouseCode == detail.WarehouseCode))
                        .Select(x => x.Id.ToString())
                        .ToList();
                    // è®¡ç®—该托盘该物料的总累计已出库数量(跨所有单据)
                    decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
                    lockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = request.OrderNo,
                        OrderDetailIds = string.Join(",", allDetailIds), // è®°å½•该物料的所有明细ID
                        OrderType = outboundOrder.OrderType,
                        BatchNo = detail.BatchNo,
                        MaterielCode = detail.MaterielCode,
                        MaterielName = detail.MaterielName,
                        StockId = stockInfo.Id,
                        OrderQuantity = allDetailIds.SelectMany(id => allDetailIds).Count() > 1
                            ? (outboundOrder.Details.Where(x =>
                                x.OrderId == outboundOrder.Id &&
                                x.MaterielCode == detail.MaterielCode &&
                                x.BatchNo == detail.BatchNo &&
                                x.SupplyCode == detail.SupplyCode &&
                                x.WarehouseCode == detail.WarehouseCode))
                                .Sum(x => x.OrderQuantity)
                            : detail.OrderQuantity, // å¦‚果只有一个明细,使用明细数量
                        OriginalQuantity = originalQuantity,
                        AssignQuantity = actualAllocatedQuantity, // æœ¬æ¬¡åˆ†é…æ•°é‡
                        AllocatedQuantity = totalAllocatedQuantity, // æœ¬æ¬¡åˆ†é…å‰çš„æ€»ç´¯è®¡å·²å‡ºåº“数量(跨所有单据)
                        LocationCode = stockInfo.LocationCode,
                        PalletCode = stockInfo.PalletCode,
                        Unit = detail.Unit,
                        OutboundTargetLocation = request.OutboundTargetLocation,
                        Status = OutLockStockStatusEnum.已分配.ObjToInt(),
                        SupplyCode = detail.SupplyCode,
                        WarehouseCode = detail.WarehouseCode,
                        FactoryArea = outboundOrder.FactoryArea,
                        TaskNum = Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(),
                        OrderDetailId = 0 // æœªå…³è”具体明细ID
                    };
                    lockInfoList.Add(lockInfo);
                }
            }
            return (actualAllocatedQuantity, lockInfoList);
        }
        /// <summary>
        /// è®¡ç®—该托盘累计已分配数量
        /// </summary>
        /// <param name="lockInfos"></param>
        /// <param name="stockId"></param>
        /// <param name="materielCode"></param>
        /// <returns></returns>
        private decimal CalcTotalAllocatedQuantity(List<Dt_OutStockLockInfo> lockInfos, int stockId, string materielCode)
        {
            // æŸ¥è¯¢è¯¥æ‰˜ç›˜è¯¥ç‰©æ–™åœ¨æ‰€æœ‰é”å®šè®°å½•中的最大已分配数量
            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
                x.StockId == stockId &&
                x.MaterielCode == materielCode);
            if (lockRecords == null || !lockRecords.Any())
            {
                return 0;
            }
            // è¿”回累计已分配数量
            return lockRecords.Sum(x => x.AssignQuantity);
        }
        /// <summary>
        /// æ›´æ–°å‡ºåº“单状态
        /// </summary>
        public bool UpdateOutboundOrderStatus(string orderNo, int status)
        {
            try
            {
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
                if (outboundOrder == null) return false;
                outboundOrder.OrderStatus = status;
                if(outboundOrder.CreateType == OrderCreateTypeEnum.CreateInSystem.ObjToInt())
                {
                    outboundOrder.ReturnToMESStatus = 5;
                }
                _outboundRepository.UpdateData(outboundOrder);
                return true;
            }
@@ -772,22 +819,12 @@
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="palletCodes"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public bool UpdateStockStatus(List<string> palletCodes, int status)
        {
            try
            {
                List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x => palletCodes.Contains(x.PalletCode));
                stockInfos.ForEach(stockInfo =>
                {
                    stockInfo.StockStatus = status;
                });
                stockInfos.ForEach(stockInfo => stockInfo.StockStatus = status);
                _stockInfoRepository.UpdateData(stockInfos);
                return true;
            }
@@ -797,22 +834,12 @@
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="locationCodes"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public bool UpdateLocationStatus(List<string> locationCodes, int status)
        {
            try
            {
                List<Dt_LocationInfo> locationInfos = _locationInfoRepository.QueryData(x => locationCodes.Contains(x.LocationCode));
                locationInfos.ForEach(x =>
                {
                    x.LocationStatus = status;
                });
                locationInfos.ForEach(x => x.LocationStatus = status);
                _locationInfoRepository.UpdateData(locationInfos);
                return true;
            }
@@ -822,21 +849,14 @@
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="outStockLockInfos"></param>
        /// <returns></returns>
        public bool UpdateOutStockLockInfo(List<Dt_OutStockLockInfo> outStockLockInfos)
        {
            try
            {
                List<Dt_OutStockLockInfo> updateData = outStockLockInfos.Where(x => x.Id > 0).ToList();
                _outboundLockInfoRepository.UpdateData(updateData);
                List<Dt_OutStockLockInfo> addData = outStockLockInfos.Where(x => x.Id <= 0).ToList();
                _outboundLockInfoRepository.AddData(addData);
                return true;
            }
            catch
@@ -870,22 +890,6 @@
                    return WebResponseContent.Instance.Error(response.Message);
                }
                bool isMatMixed = stockInfo.Details.GroupBy(x => new
                {
                    x.MaterielCode,
                    x.MaterielName,
                    x.BatchNo,
                    x.SupplyCode,
                    x.WarehouseCode
                }).Count() > 1;
                if (isMatMixed)
                {
                    response.Success = false;
                    response.Message = $"混料托盘 {request.PalletCode} ä¸èƒ½æ•´ç®±å‡ºåº“";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                // 2. æŸ¥æ‰¾å‡ºåº“单信息
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
                if (outboundOrder == null)
@@ -908,6 +912,67 @@
                {
                    response.Success = false;
                    response.Message = $"该库存没有分配出库量,托盘号:{request.PalletCode}";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                //bool isMatMixed = stockInfo.Details.GroupBy(x => new
                //{
                //    x.MaterielCode,
                //    x.MaterielName,
                //    x.BatchNo,
                //    x.SupplyCode,
                //    x.WarehouseCode
                //}).Count() > 1;
                bool isMatMixed = false;
                bool includeBatchNo = !string.IsNullOrEmpty(lockInfo.BatchNo);
                bool includeSupplyCode = !string.IsNullOrEmpty(lockInfo.SupplyCode);
                if (includeBatchNo && includeSupplyCode)
                {
                    isMatMixed = stockInfo.Details.GroupBy(x => new
                    {
                        x.MaterielCode,
                        x.MaterielName,
                        x.BatchNo,
                        x.SupplyCode,
                        x.WarehouseCode
                    }).Count() > 1;
                }
                else if (includeBatchNo && !includeSupplyCode)
                {
                    isMatMixed = stockInfo.Details.GroupBy(x => new
                    {
                        x.MaterielCode,
                        x.MaterielName,
                        x.BatchNo,
                        x.WarehouseCode
                    }).Count() > 1;
                }
                else if (!includeBatchNo && includeSupplyCode)
                {
                    isMatMixed = stockInfo.Details.GroupBy(x => new
                    {
                        x.MaterielCode,
                        x.MaterielName,
                        x.SupplyCode,
                        x.WarehouseCode
                    }).Count() > 1;
                }
                else
                {
                    isMatMixed = stockInfo.Details.GroupBy(x => new
                    {
                        x.MaterielCode,
                        x.MaterielName,
                        x.WarehouseCode
                    }).Count() > 1;
                }
                if (isMatMixed)
                {
                    response.Success = false;
                    response.Message = $"混料托盘 {request.PalletCode} ä¸èƒ½æ•´ç®±å‡ºåº“";
                    return WebResponseContent.Instance.Error(response.Message);
                }
@@ -960,7 +1025,7 @@
                    // æ•´ç®±å‡ºåº“无需拆包
                    PerformFullOutboundOperation(stockInfo, request, lockInfo.TaskNum.GetValueOrDefault());
                    if (outboundOrder.OrderType != 0)
                    if (outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
                    {
                        Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.OrderNo == outboundOrder.OrderNo);
                        if (allocateOrder != null)
@@ -987,6 +1052,29 @@
                            _allocateMaterialInfoRepository.AddData(allocateMaterialInfos);
                        }
                    }
                    else if(outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt())
                    {
                        List<Dt_AllocateMaterialInfo> allocateMaterialInfos = new List<Dt_AllocateMaterialInfo>();
                        foreach (var item in stockInfo.Details)
                        {
                            Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo()
                            {
                                Barcode = item.Barcode??"",
                                BatchNo = item.BatchNo,
                                FactoryArea = item.FactoryArea,
                                MaterialCode = item.MaterielCode,
                                MaterialName = item.MaterielName,
                                OrderId = outboundOrder.Id,
                                OrderNo = outboundOrder.OrderNo,
                                Quantity = item.StockQuantity,
                                SupplyCode = item.SupplyCode??"",
                                Unit = item.Unit,
                                WarehouseCode = item.WarehouseCode??""
                            };
                            allocateMaterialInfos.Add(allocateMaterialInfo);
                        }
                        _allocateMaterialInfoRepository.AddData(allocateMaterialInfos);
                    }
                    decimal allocatedQuantity = actualOutboundQuantity;
                    List<Dt_OutboundOrderDetail> updateDetails = new();
@@ -1007,34 +1095,47 @@
                        //}
                        List<Barcodes> barcodesList = new List<Barcodes>();
                        List<Dt_StockInfoDetail> stockInfoDetails = stockInfo.Details.Where((x => x.StockQuantity > x.OutboundQuantity)).ToList();
                        decimal itemQuantity = item.LockQuantity - item.OverOutQuantity;
                        decimal unitbarcodeQuantity;
                        foreach (var stockDetail in stockInfoDetails)
                        {
                            if (item.LockQuantity - item.OverOutQuantity >= stockDetail.StockQuantity - stockInfoDetail.OutboundQuantity)
                            if (itemQuantity >= stockDetail.StockQuantity - stockDetail.OutboundQuantity)
                            {
                                unitbarcodeQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity;
                                UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, unitbarcodeQuantity);
                                Barcodes barcodes = new Barcodes
                                {
                                    Barcode = stockDetail.Barcode,
                                    Qty = stockDetail.StockQuantity - stockInfoDetail.OutboundQuantity,
                                    Qty = currentResult.ToQuantity,
                                    SupplyCode = stockDetail?.SupplyCode ?? "",
                                    BatchNo = stockDetail?.BatchNo ?? "",
                                    Unit = stockDetail?.Unit ?? ""
                                    Unit = currentResult.ToUnit ?? ""
                                };
                                stockDetail.StockQuantity = stockInfoDetail.OutboundQuantity;
                                itemQuantity -= (stockDetail.StockQuantity - stockDetail.OutboundQuantity);
                                stockDetail.OutboundQuantity = stockDetail.StockQuantity;
                                barcodesList.Add(barcodes);
                                if (itemQuantity <= 0) break;
                            }
                            else
                            {
                                UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, itemQuantity);
                                Barcodes barcodes = new Barcodes
                                {
                                    Barcode = stockDetail.Barcode,
                                    Qty = item.LockQuantity - item.OverOutQuantity,
                                    Qty = currentResult.ToQuantity,
                                    SupplyCode = stockDetail?.SupplyCode ?? "",
                                    BatchNo = stockDetail?.BatchNo ?? "",
                                    Unit = stockDetail?.Unit ?? ""
                                    Unit = currentResult.ToUnit ?? ""
                                };
                                stockInfoDetail.OutboundQuantity += item.LockQuantity - item.OverOutQuantity;
                                stockDetail.OutboundQuantity += itemQuantity;
                                barcodesList.Add(barcodes);
                                break;
                            }
                        }
@@ -1050,8 +1151,15 @@
                        {
                            barcodeQuantity = item.LockQuantity - item.OverOutQuantity;
                            allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
                            if(item.ReturnToMESStatus == 0)
                            {
                                item.CurrentDeliveryQty = item.LockQuantity;
                            }
                            else
                            {
                                item.CurrentDeliveryQty += item.LockQuantity - item.OverOutQuantity;
                            }
                            item.OverOutQuantity = item.LockQuantity;
                            item.CurrentDeliveryQty = item.LockQuantity;
                        }
                        updateDetails.Add(item);
@@ -1067,6 +1175,11 @@
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        };
                        item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings);
                        //重拣出库不需要回传
                        if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt())
                        {
                            item.ReturnJsonData = "";
                        }
                    }
                    lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
@@ -1094,12 +1207,31 @@
                    response.Message = "出库完成";
                    response.UpdatedDetails = updateDetails;
                    if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails))
                    {
                        Func<Dt_OutStockLockInfo, bool> supWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().SupplyCode) ? true : x.SupplyCode == outboundOrderDetails.First().SupplyCode;
                        Func<Dt_OutStockLockInfo, bool> wareWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().WarehouseCode) ? true : x.WarehouseCode == outboundOrderDetails.First().WarehouseCode;
                        List<Dt_OutStockLockInfo> stockLockInfos = _outboundLockInfoRepository.QueryData(x =>
                                x.OrderNo == request.OrderNo &&
                                x.MaterielCode == stockInfoDetail.MaterielCode).Where(supWhere).Where(wareWhere).ToList();
                        if (stockLockInfos != null && stockLockInfos.Any())
                        {
                            _outboundLockInfoRepository.DeleteAndMoveIntoHty(stockLockInfos, WIDESEA_Core.Enums.OperateTypeEnum.自动删除);
                        }
                    }
                    // æ£€æŸ¥å‡ºåº“单是否完成
                    if (CheckOutboundOrderCompleted(request.OrderNo))
                    {
                        UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
                        _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                        if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt()&& outboundOrder.OrderType!= InOrderTypeEnum.ReCheck.ObjToInt())
                        {
                            _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                        }
                    }
                }
                catch (Exception ex)
@@ -1324,7 +1456,7 @@
                        MaterialCodeReturnDTO returnDTO = returnDTOs.First(x => x.Barcode == newBarcode);
                        if (outboundOrder.OrderType != 0)
                        if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt()||outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
                        {
                            allocateMaterialInfo = new Dt_AllocateMaterialInfo()
                            {
@@ -1346,7 +1478,7 @@
                    {
                        PerformFullOutboundOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault());
                        if (outboundOrder.OrderType != 0)
                        if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt() || outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
                        {
                            allocateMaterialInfo = new Dt_AllocateMaterialInfo()
                            {
@@ -1365,8 +1497,8 @@
                    }
                    // åˆ¤æ–­æ˜¯å¦æ˜¯è°ƒæ‹¨å•
                    if (outboundOrder.OrderType != 0)
                    // åˆ¤æ–­æ˜¯å¦æ˜¯æ™ºä»“调智仓单
                    if ( outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
                    {
                        Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.OrderNo == outboundOrder.OrderNo);
                        if (allocateOrder != null)
@@ -1382,7 +1514,6 @@
                    foreach (var item in outboundOrderDetails)
                    {
                        if (allocatedQuantity <= 0) break;
                        //if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity)
                        //{
@@ -1407,20 +1538,29 @@
                        {
                            barcodeQuantity = item.LockQuantity - item.OverOutQuantity;
                            allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
                            if (item.ReturnToMESStatus == 0)
                            {
                                item.CurrentDeliveryQty = item.LockQuantity;
                            }
                            else
                            {
                                item.CurrentDeliveryQty += item.LockQuantity - item.OverOutQuantity;
                            }
                            item.OverOutQuantity = item.LockQuantity;
                            item.CurrentDeliveryQty = item.LockQuantity;
                        }
                        updateDetails.Add(item);
                        List<Barcodes> barcodesList = new List<Barcodes>();
                        UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, barcodeQuantity);
                        Barcodes barcodes = new Barcodes
                        {
                            Barcode = request.Barcode,
                            Qty = barcodeQuantity,
                            Barcode = isUnpacked ? newBarcode : stockDetail?.Barcode,
                            Qty = currentResult.ToQuantity,
                            SupplyCode = stockDetail?.SupplyCode ?? "",
                            BatchNo = stockDetail?.BatchNo ?? "",
                            Unit = stockDetail?.Unit ?? ""
                            Unit = currentResult.ToUnit ?? ""
                        };
                        if (!string.IsNullOrEmpty(item.ReturnJsonData))
                        {
@@ -1436,7 +1576,7 @@
                    lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
                    if (lockInfo.SortedQuantity == lockInfo.AssignQuantity)
                    if (lockInfo.SortedQuantity >= lockInfo.AssignQuantity)
                    {
                        _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.自动完成);
                    }
@@ -1482,12 +1622,31 @@
                        _feedbackMesService.BarcodeFeedback(newBarcode);
                    }
                    if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails))
                    {
                        Func<Dt_OutStockLockInfo, bool> supWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().SupplyCode) ? true : x.SupplyCode == outboundOrderDetails.First().SupplyCode;
                        Func<Dt_OutStockLockInfo, bool> wareWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().WarehouseCode) ? true : x.WarehouseCode == outboundOrderDetails.First().WarehouseCode;
                        List<Dt_OutStockLockInfo> stockLockInfos = _outboundLockInfoRepository.QueryData(x =>
                                x.OrderNo == request.OrderNo &&
                                x.MaterielCode == stockDetail.MaterielCode).Where(supWhere).Where(wareWhere).ToList();
                        if (stockLockInfos != null && stockLockInfos.Any())
                        {
                            _outboundLockInfoRepository.DeleteAndMoveIntoHty(stockLockInfos, WIDESEA_Core.Enums.OperateTypeEnum.自动删除);
                        }
                    }
                    // æ£€æŸ¥å‡ºåº“单是否完成
                    if (CheckOutboundOrderCompleted(request.OrderNo))
                    {
                        UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
                        _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                        if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt() && outboundOrder.CreateType!=OrderCreateTypeEnum.CreateInSystem.ObjToInt())
                        {
                            _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                        }
                    }
                }
                catch (Exception ex)
@@ -1536,6 +1695,8 @@
        /// </summary>
        private decimal CalculateActualOutboundQuantity(Dt_StockInfoDetail stockDetail, List<Dt_OutboundOrderDetail> outboundDetails, Dt_OutStockLockInfo lockInfo)
        {
            // decimal availableOutboundQuantity = lockInfo.AssignQuantity - lockInfo.SortedQuantity;
            decimal availableOutboundQuantity = lockInfo.AssignQuantity;
            decimal detailRemainingQuantity = outboundDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);//outboundDetail.OrderQuantity - outboundDetail.OverOutQuantity;
@@ -1554,7 +1715,7 @@
        /// <param name="beforeQuantity"></param>
        /// <param name="taskNum"></param>
        /// <returns></returns>
        private (string NewBarcode, List<MaterialCodeReturnDTO> MaterialCodeReturnDTOs) PerformUnpackOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
        public (string NewBarcode, List<MaterialCodeReturnDTO> MaterialCodeReturnDTOs) PerformUnpackOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
            decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum, int orderId, string orderNo)
        {
            string newBarcode = GenerateNewBarcode();
@@ -1632,7 +1793,7 @@
        /// <summary>
        /// æ‰§è¡Œå®Œæ•´å‡ºåº“操作(不拆包)
        /// </summary>
        private void PerformFullOutboundOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
        public void PerformFullOutboundOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
            decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum)
        {
            // ä¿å­˜åº“存明细到历史记录
@@ -1812,6 +1973,36 @@
            return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty);
        }
        /// <summary>
        /// æ£€æŸ¥å‡ºåº“单明细是否完成
        /// </summary>
        public bool CheckOutboundOrderDetailCompletedByMatCode(string orderNo, string materialCode, List<Dt_OutboundOrderDetail> outboundOrderDetails)
        {
            if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(materialCode) || outboundOrderDetails == null || !outboundOrderDetails.Any())
                return false;
            // æŸ¥è¯¢ä¸»è®¢å•,不存在直接返回false
            Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
            if (outboundOrder == null) return false;
            var firstDetail = outboundOrderDetails.FirstOrDefault();
            string supplyCode = firstDetail.SupplyCode;
            string warehouseCode = firstDetail.WarehouseCode;
            List<int> ids = outboundOrderDetails.Select(x => x.Id).ToList();
            List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x =>
                x.OrderId == outboundOrder.Id
                && x.MaterielCode == materialCode
                && ids.Contains(x.Id)
                && (string.IsNullOrEmpty(supplyCode) || x.SupplyCode == supplyCode)
                && (string.IsNullOrEmpty(warehouseCode) || x.WarehouseCode == warehouseCode)
            );
            if (!details.Any()) return false;
            return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty);
        }
        #endregion
        #region å–空箱
@@ -1821,6 +2012,12 @@
            try
            {
                var stock = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Includes(x => x.Details).Where(x => x.PalletCode == palletCode).FirstAsync();
                Dt_Task task = _taskRepository.QueryFirst(x => x.PalletCode == palletCode);
                if (task != null)
                {
                    return WebResponseContent.Instance.Error("任务信息列表存在该托盘的任务信息,不可取走空箱,请检查任务是否完成");
                }
                if (stock == null)
                {
@@ -1866,21 +2063,20 @@
                if (stock.Details.Count <= 0)
                {
                    stock.PalletType = (int)PalletTypeEnum.Empty;
                    stock.StockStatus = (int)StockStatusEmun.组盘暂存;
                    stock.StockStatus = (int)StockStatusEmun.入库确认;
                    stock.LocationCode = "";
                }
                else if (stock.Details.Count > 0)
                {
                    Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
                       x.OrderNo == OrderNo &&
                       x.StockId == stock.Id &&
                       x.PalletCode == palletCode);
                    if (lockInfo != null && lockInfo.SortedQuantity != lockInfo.AssignQuantity)
                    {
                        return content.Error($"托盘{palletCode}库存未拣选完不允许回库");
                        return content.Error($"托盘{palletCode}库存,在单据{lockInfo.OrderNo}里面还未拣选完成,不允许回库");
                    }
                    stock.StockStatus = (int)StockStatusEmun.组盘暂存;
                    stock.StockStatus = (int)StockStatusEmun.入库确认;
                    stock.LocationCode = "";
                }
@@ -1896,15 +2092,20 @@
                // åˆ†é…æ–°è´§ä½
                var newLocation = _locationInfoService.AssignLocation(stock.LocationType);
                if(newLocation == null)
                {
                    return WebResponseContent.Instance.Error("没有空闲库位可回库");
                }
                var newTask = new Dt_Task()
                {
                    CurrentAddress = stations[station],
                    CurrentAddress = stations.GetValueOrDefault(station) ?? "",
                    Grade = 0,
                    PalletCode = palletCode,
                    NextAddress = "",
                    OrderNo = OrderNo,
                    Roadway = newLocation.RoadwayNo,
                    SourceAddress = stations[station],
                    SourceAddress = stations.GetValueOrDefault(station) ?? "",
                    TargetAddress = newLocation.LocationCode,
                    TaskStatus = (int)TaskStatusEnum.New,
                    TaskType = stock.Details.Count > 0 ? (int)TaskTypeEnum.InPick : (int)TaskTypeEnum.InEmpty,
@@ -1914,11 +2115,11 @@
                _stockInfoRepository.UpdateData(stock);
                _taskRepository.AddData(newTask);
                //var moveResult = await _eSSApiService.MoveContainerAsync(new MoveContainerRequest
                //{
                //    slotCode = movestations[station],
                //    containerCode = palletCode
                //});
                var moveResult = await _eSSApiService.MoveContainerAsync(new MoveContainerRequest
                {
                    slotCode = movestations[station],
                    containerCode = palletCode
                });
                return content.OK();
            }
@@ -1928,6 +2129,67 @@
            }
        }
        public WebResponseContent RecheckPicking(RecheckPickingDTO pickingDTO)
        {
            try
            {
                Dt_ReCheckOrder reCheckOrder = _outboundRepository.Db.Queryable<Dt_ReCheckOrder>().Where(x => x.OrderNo == pickingDTO.orderNo && x.Result == 0).First();
                if(reCheckOrder == null)
                {
                    return WebResponseContent.Instance.Error($"未找到该待重拣的单据{pickingDTO.orderNo}");
                }
                Dt_StockInfoDetail stockInfoDetail = _stockDetailRepository.QueryFirst(x=>x.Barcode == pickingDTO.barCode && x.Status == StockStatusEmun.手动冻结.ObjToInt() );
                if(stockInfoDetail == null)
                {
                    return WebResponseContent.Instance.Error($"未在库存中找到该冻结/隔离条码 {pickingDTO.barCode}");
                }
                if (stockInfoDetail.MaterielCode != reCheckOrder.MaterielCode || stockInfoDetail.BatchNo != reCheckOrder.BatchNo)
                {
                    return WebResponseContent.Instance.Error("该条码的物料编码和批次和该重检单不符");
                }
                stockInfoDetail.OrderNo = pickingDTO.orderNo;
                stockInfoDetail.Status = StockStatusEmun.重检中.ObjToInt();
                var currentRemark = _outboundRepository.Db.Queryable<Dt_OutboundOrder>()
                .Where(x => x.OrderNo == pickingDTO.orderNo)
                .Select(x => x.Remark)
                .First();
                string newRemark;
                if (string.IsNullOrWhiteSpace(currentRemark))
                {
                    newRemark = pickingDTO.barCode;
                }
                else
                {
                    var existingCodes = currentRemark.Split(',', StringSplitOptions.RemoveEmptyEntries)
                        .Select(s => s.Trim())
                        .ToList();
                    if (!existingCodes.Contains(pickingDTO.barCode))
                    {
                        existingCodes.Add(pickingDTO.barCode);
                        newRemark = string.Join(",", existingCodes);
                    }
                    else
                    {
                        newRemark = currentRemark;
                    }
                }
                _outboundRepository.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => x.Remark == newRemark)
                    .SetColumns(x=>x.OrderStatus == (int)OutOrderStatusEnum.出库完成)
                    .Where(x => x.OrderNo == pickingDTO.orderNo)
                    .ExecuteCommand();
                _stockDetailRepository.UpdateData(stockInfoDetail);
                return WebResponseContent.Instance.OK();
            }
            catch(Exception ex)
            {
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        #endregion
    }
}