heshaofeng
2026-03-09 557f7f6079c30cd6fe8d6005cea3d89468bbcd31
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -2,11 +2,14 @@
using Dm.filter;
using MailKit.Search;
using Mapster;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
using Org.BouncyCastle.Asn1.Ocsp;
using Org.BouncyCastle.Crypto;
using SqlSugar;
using System;
using System.Reflection.Emit;
using WIDESEA_BasicService;
using WIDESEA_Common.CommonEnum;
@@ -18,14 +21,19 @@
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.Basic;
using WIDESEA_Model.Models.Check;
using static HslCommunication.Profinet.Knx.KnxCode;
namespace WIDESEA_OutboundService
{
@@ -56,6 +64,12 @@
        private readonly IRepository<Dt_Task> _taskRepository;
        private readonly ILocationInfoService _locationInfoService;
        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;
        public readonly IRepository<Dt_WarehouseArea> _warehouseAreaRepository;
        public readonly IRepository<Dt_LocationType> _locationTypeRepository;
        private Dictionary<string, string> stations = new Dictionary<string, string>
        {
@@ -69,7 +83,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)
        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, IRepository<Dt_LocationType> locationTypeRepository, IRepository<Dt_WarehouseArea> warehouseAreaRepository)
        {
            _mapper = mapper;
            _unitOfWorkManage = unitOfWorkManage;
@@ -90,6 +104,39 @@
            _taskRepository = taskRepository;
            _locationInfoService = locationInfoService;
            _eSSApiService = eSSApiService;
            _allocateOrderRepository = allocateOrderRepository;
            _allocateMaterialInfoRepository = allocateMaterialInfoRepository;
            _inboundOrderDetailRepository = inboundOrderDetailRepository;
            _inboundOrderRepository = inboundOrderRepository;
            _locationTypeRepository = locationTypeRepository;
            _warehouseAreaRepository = warehouseAreaRepository;
        }
        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 å‡ºåº“分配
@@ -101,137 +148,276 @@
        public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request)
        {
            WebResponseContent content = WebResponseContent.Instance;
            PickingOutboundResponseDTO response = new PickingOutboundResponseDTO();
            decimal totalNeedAllocate = 0; // æ€»éœ€æ±‚分配量
            decimal totalActualAllocate = 0; // å®žé™…总分配量
            string targetWarehouse = string.Empty;// ç›®æ ‡ä»“库
            string targetLocationCode = string.Empty; // ç›®æ ‡è´§ä½
            bool isWholeCaseOutbound = false; // æ˜¯å¦æ•´ç®±å‡ºåº“
            List<string> wholeCasePallets = new List<string>();
            Dictionary<string, string> palletLocationMap = new Dictionary<string, string>();
            Dictionary<string, bool> palletIsWholeCaseMap = new Dictionary<string, bool>();
            int? targetLocationType = null;
            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);
                    }
                        if (outboundOrder.OrderType == 117)
                        {
                            // åŒ¹é…å½“前单据的锁定记录
                            if (outboundOrder.OrderNo == item.OrderNo)
                            {
                                // æŸ¥è¯¢åº“存信息
                                var stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == item.PalletCode);
                                if (stockInfo == null)
                                {
                                    content = WebResponseContent.Instance.Error($"托盘{item.PalletCode}未查询到库存信息,无法处理整箱出库");
                                    _unitOfWorkManage.RollbackTran();
                                    return content;
                                }
                                // è®¡ç®—库存总量,判断是否整箱(增加0值保护)
                                decimal stockQuantity = _stockDetailRepository.QueryData(x => x.StockId == stockInfo.Id).Sum(x => x.StockQuantity);
                                if (stockQuantity > 0 && stockQuantity == item.AssignQuantity)
                                {
                                    // æ ‡è®°å½“前托盘为整箱(核心修复:按托盘维度记录状态)
                                    if (!palletIsWholeCaseMap.ContainsKey(item.PalletCode))
                                    {
                                        palletIsWholeCaseMap.Add(item.PalletCode, true);
                                    }
                                    else
                                    {
                                        palletIsWholeCaseMap[item.PalletCode] = true;
                                    }
                                    // ç›®æ ‡ä»“库/货位类型只查询一次(性能优化+空值保护)
                                    if (string.IsNullOrEmpty(targetWarehouse))
                                    {
                                        targetWarehouse = GetToWarehouseByOrderNo(request.OrderNo);
                                        if (string.IsNullOrEmpty(targetWarehouse))
                                        {
                                            content = WebResponseContent.Instance.Error("智仓调智仓整箱出库单据未配置目标仓库");
                                            _unitOfWorkManage.RollbackTran();
                                            return content;
                                        }
                                        // æ›¿æ¢First()为FirstOrDefault(),避免空值异常
                                        string warehouseAreaName = _warehouseAreaRepository.Db.Queryable<Dt_WarehouseArea>()
                                            .Where(x => x.Code == targetWarehouse)
                                            .Select(x => x.Name)
                                            .First();
                                        if (string.IsNullOrEmpty(warehouseAreaName))
                                        {
                                            content = WebResponseContent.Instance.Error($"目标仓库{targetWarehouse}未查询到对应的库区名称");
                                            _unitOfWorkManage.RollbackTran();
                                            return content;
                                        }
                                        int? locationType = _locationTypeRepository.Db.Queryable<Dt_LocationType>()
                                            .Where(x => string.Equals(x.LocationTypeDesc, warehouseAreaName, StringComparison.OrdinalIgnoreCase))
                                            .Select(x => x.LocationType)
                                            .First();
                                        if (!locationType.HasValue)
                                        {
                                            content = WebResponseContent.Instance.Error($"库区{warehouseAreaName}未匹配到对应的货位类型");
                                            _unitOfWorkManage.RollbackTran();
                                            return content;
                                        }
                                        targetLocationType = locationType.Value;
                                    }
                                    // åˆ†é…ç›®æ ‡è´§ä½ï¼ˆæ¯ä¸ªæ‰˜ç›˜ç‹¬ç«‹è´§ä½ï¼‰
                                    if (!palletLocationMap.ContainsKey(item.PalletCode) && targetLocationType.HasValue)
                                    {
                                        Dt_LocationInfo locationInfo = _locationInfoService.AssignLocation(targetLocationType.Value);
                                        if (locationInfo == null || string.IsNullOrEmpty(locationInfo.LocationCode))
                                        {
                                            content = WebResponseContent.Instance.Error($"货位类型{targetLocationType.Value}未分配到可用货位(托盘:{item.PalletCode})");
                                            _unitOfWorkManage.RollbackTran();
                                            return content;
                                        }
                                        palletLocationMap.Add(item.PalletCode, locationInfo.LocationCode);
                                    }
                                    // åŠ å…¥æ•´ç®±æ‰˜ç›˜åˆ—è¡¨
                                    if (!wholeCasePallets.Contains(item.PalletCode))
                                    {
                                        wholeCasePallets.Add(item.PalletCode);
                                    }
                                }
                                else
                                {
                                    if (!palletIsWholeCaseMap.ContainsKey(item.PalletCode))
                                    {
                                        palletIsWholeCaseMap.Add(item.PalletCode, false);
                                    }
                                    else
                                    {
                                        palletIsWholeCaseMap[item.PalletCode] = false;
                                    }
                                }
                            }
                          }
                        }
                    // å¤„理任务
                    foreach (var item in materielPickedDetails.Tasks)
                    {
                        Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                        if (task == null)
                        if (outboundOrder.OrderType == 117
                           && palletIsWholeCaseMap.ContainsKey(item.PalletCode)
                           && palletIsWholeCaseMap[item.PalletCode]
                           && palletLocationMap.ContainsKey(item.PalletCode))
                        {
                            tasks.Add(item);
                            item.TaskType = (int)TaskTypeEnum.Relocation;
                            item.TargetAddress = palletLocationMap[item.PalletCode];
                        }
                        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;
                        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;
                    }
                }
                        decimal lockQuantity = (detail.OrderQuantity - detail.OverOutQuantity);
                        if (lockQuantity < materielCalc.UnallocatedQuantity)
                // 3. æ‰¹é‡æ›´æ–°çŠ¶æ€ï¼ˆåŽŸæœ‰é€»è¾‘ä¸å˜ï¼‰
                UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.出库中);
                _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);
                if (outboundOrder.OrderType == 117 && wholeCasePallets.Any())
                {
                    foreach (var palletCode in wholeCasePallets)
                    {
                        var completeReq = new OutboundCompletePalletRequestDTO
                        {
                            detail.LockQuantity += lockQuantity; // å¢žåŠ é”å®šæ•°é‡ ä¸æ›´æ–° OverOutQuantity å’Œ OrderDetailStatus,因为还没有实际出库
                            outboundOrderDetails.Add(detail);
                            materielCalc.UnallocatedQuantity -= lockQuantity;
                        }
                        else
                            OrderNo = request.OrderNo,
                            PalletCode = palletCode
                        };
                        var res = CompleteOutboundWithPallet(completeReq);
                        if (!res.Status)
                        {
                            detail.LockQuantity += materielCalc.UnallocatedQuantity;
                            outboundOrderDetails.Add(detail);
                            break;
                            _unitOfWorkManage.RollbackTran();
                            return res;
                        }
                    }
                }
                // 3. æ›´æ–°å‡ºåº“单状态为出库中(表示已有任务分配)
                UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.出库中);
                // 4. æ›´æ–°å‡ºåº“单明细锁定数量
                _detailRepository.UpdateData(outboundOrderDetails);
                // 5. æ›´æ–°åº“存状态
                UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.出库锁定.ObjToInt());
                // 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>
        /// <param name="orderNo"></param>
        /// <returns></returns>
        public String GetToWarehouseByOrderNo(string orderNo)
        {
            var order =_allocateOrderRepository.QueryFirst(x => x.OrderNo == orderNo);
            return order.ToWarehouse;
        }
        /// <summary>
        /// è®¡ç®—出库数量逻辑(原有逻辑不变)
        /// </summary>
        public OutboundCalculationDTO CalcOutboundQuantity(PickingOutboundRequestDTO request)
        {
            OutboundCalculationDTO result = new();
            OutboundCalculationDTO result = new OutboundCalculationDTO();
            try
            {
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
@@ -243,15 +429,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)
                List<Dt_OutboundOrderDetail> selectedDetails = new List<Dt_OutboundOrderDetail>();
                if (request.DetailIds == null || !request.DetailIds.Any())
                {
                    selectedDetails = _detailRepository.QueryData(x => 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())
                {
@@ -262,23 +448,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)
                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;
@@ -290,10 +474,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;
@@ -307,10 +489,7 @@
                    {
                        inputQuantity -= (item.OrderQuantity - item.MoveQty - item.LockQuantity);
                        outboundOrderDetails.Add(item);
                        if (inputQuantity <= 0)
                        {
                            break;
                        }
                        if (inputQuantity <= 0) break;
                    }
                    result.MaterielCalculations = new List<MaterielOutboundCalculationDTO>()
@@ -330,35 +509,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
@@ -366,7 +537,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,
@@ -376,136 +546,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}");
            }
            // éœ€åˆ†é…æ•°é‡
            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,
@@ -520,241 +949,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;
            }
@@ -764,22 +970,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;
            }
@@ -789,22 +985,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;
            }
@@ -814,21 +1000,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
@@ -838,8 +1017,7 @@
        }
        #endregion
        #region æ•´ç®±å‡ºåº“
        public WebResponseContent CompleteOutboundWithPallet(OutboundCompletePalletRequestDTO request)
        {
            WebResponseContent content = WebResponseContent.Instance;
@@ -860,22 +1038,6 @@
                {
                    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;
                if (isMatMixed)
                {
                    response.Success = false;
                    response.Message = $"混料托盘 {request.PalletCode} ä¸èƒ½æ•´ç®±å‡ºåº“";
                    return WebResponseContent.Instance.Error(response.Message);
                }
@@ -901,6 +1063,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);
                }
@@ -953,6 +1176,56 @@
                    // æ•´ç®±å‡ºåº“无需拆包
                    PerformFullOutboundOperation(stockInfo, request, lockInfo.TaskNum.GetValueOrDefault());
                    if (outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
                    {
                        Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.OrderNo == outboundOrder.OrderNo);
                        if (allocateOrder != null)
                        {
                            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 = allocateOrder.ToWarehouse
                                };
                                allocateMaterialInfos.Add(allocateMaterialInfo);
                            }
                            _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();
@@ -973,34 +1246,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;
                            }
                        }
@@ -1016,8 +1302,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);
@@ -1033,6 +1326,11 @@
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        };
                        item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings);
                        //重拣出库不需要回传
                        if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt())
                        {
                            item.ReturnJsonData = "";
                        }
                    }
                    lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
@@ -1060,13 +1358,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());
                        if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt()&& outboundOrder.OrderType!= InOrderTypeEnum.ReCheck.ObjToInt())
                        {
                            _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                        }
                        //todo: å›žä¼ MES
                    }
                }
                catch (Exception ex)
@@ -1131,6 +1447,8 @@
                    FactoryArea = item.FactoryArea,
                    WarehouseCode = item.WarehouseCode,
                    Barcode = item.Barcode,
                    CreateDate = item.CreateDate,
                    Creater = item.Creater,
                    Remark = $"整箱出库完成删除,条码:{request.PalletCode},原数量:{item.StockQuantity},出库数量:{item.StockQuantity},操作者:{request.Operator}"
                };
                historyRecords.Add(historyRecord);
@@ -1160,9 +1478,16 @@
            _stockDetailHistoryRepository.AddData(historyRecords);
            // åˆ é™¤åº“存明细记录
            _stockDetailRepository.DeleteData(stockInfo.Details);
            var orderNo =_outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
            if(orderNo.OrderType != 117)
            {
                _stockDetailRepository.DeleteData(stockInfo.Details);
            }
            _stockChangeRepository.AddData(changeRecords);
        }
        #endregion
        #region æ‹£é€‰
        /// <summary>
@@ -1269,21 +1594,76 @@
                // 8. åˆ¤æ–­æ˜¯å¦éœ€è¦æ‹†åŒ…(当出库数量小于库存数量时需要拆包)
                bool isUnpacked = actualOutboundQuantity < stockDetail.StockQuantity;
                List<MaterialCodeReturnDTO> returnDTOs = new List<MaterialCodeReturnDTO>();
                string newBarcode = string.Empty;
                // 9. å¼€å¯äº‹åŠ¡
                _unitOfWorkManage.BeginTran();
                try
                {
                    decimal beforeQuantity = stockDetail.StockQuantity; // åŽŸå§‹åº“å­˜é‡
                    Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo();
                    // æ ¹æ®æ˜¯å¦æ‹†åŒ…执行不同的操作
                    if (isUnpacked)
                    {
                        returnDTOs = PerformUnpackOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault());
                        (string NewBarcode, List<MaterialCodeReturnDTO> MaterialCodeReturnDTOs) result = PerformUnpackOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault(), outboundOrder.Id, outboundOrder.OrderNo);
                        returnDTOs = result.MaterialCodeReturnDTOs;
                        newBarcode = result.NewBarcode;
                        MaterialCodeReturnDTO returnDTO = returnDTOs.First(x => x.Barcode == newBarcode);
                        if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt()||outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
                        {
                            allocateMaterialInfo = new Dt_AllocateMaterialInfo()
                            {
                                Barcode = returnDTO.Barcode,
                                BatchNo = returnDTO.BatchNo,
                                FactoryArea = returnDTO.FactoryArea,
                                MaterialCode = returnDTO.MaterialCode,
                                MaterialName = returnDTO.MaterialName,
                                OrderId = outboundOrder.Id,
                                OrderNo = outboundOrder.OrderNo,
                                Quantity = returnDTO.Quantity,
                                SupplyCode = returnDTO.SuplierCode,
                                Unit = stockDetail.Unit
                            };
                        }
                    }
                    else
                    {
                        PerformFullOutboundOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault());
                        if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt() || outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
                        {
                            allocateMaterialInfo = new Dt_AllocateMaterialInfo()
                            {
                                Barcode = stockDetail.Barcode,
                                BatchNo = stockDetail.BatchNo,
                                FactoryArea = stockDetail.FactoryArea,
                                MaterialCode = stockDetail.MaterielCode,
                                MaterialName = stockDetail.MaterielName,
                                OrderId = outboundOrder.Id,
                                OrderNo = outboundOrder.OrderNo,
                                Quantity = stockDetail.StockQuantity,
                                SupplyCode = stockDetail.SupplyCode,
                                Unit = stockDetail.Unit
                            };
                        }
                    }
                    // åˆ¤æ–­æ˜¯å¦æ˜¯æ™ºä»“调智仓单
                    if ( outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
                    {
                        Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.OrderNo == outboundOrder.OrderNo);
                        if (allocateOrder != null)
                        {
                            allocateMaterialInfo.WarehouseCode = allocateOrder.ToWarehouse;
                            _allocateMaterialInfoRepository.AddData(allocateMaterialInfo);
                        }
                    }
                    decimal allocatedQuantity = actualOutboundQuantity;
@@ -1291,7 +1671,6 @@
                    foreach (var item in outboundOrderDetails)
                    {
                        if (allocatedQuantity <= 0) break;
                        //if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity)
                        //{
@@ -1316,20 +1695,33 @@
                        {
                            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;
                        }
                        if (item.OverOutQuantity == item.OrderQuantity)
                        {
                            item.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                        }
                        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))
                        {
@@ -1345,7 +1737,7 @@
                    lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
                    if (lockInfo.SortedQuantity == lockInfo.AssignQuantity)
                    if (lockInfo.SortedQuantity >= lockInfo.AssignQuantity)
                    {
                        _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.自动完成);
                    }
@@ -1385,13 +1777,37 @@
                    response.ScannedDetail = scannedDetail;
                    response.UpdatedDetails = updateDetails;
                    if (!string.IsNullOrEmpty(newBarcode))
                    {
                        // ç‰©æ–™æ–°æ¡ç å›žä¼ 
                        _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());
                        //todo: å›žä¼ MES
                        if (outboundOrder.CreateType!=OrderCreateTypeEnum.CreateInSystem.ObjToInt())
                        {
                            _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                        }
                    }
                }
                catch (Exception ex)
@@ -1440,6 +1856,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;
@@ -1458,14 +1876,14 @@
        /// <param name="beforeQuantity"></param>
        /// <param name="taskNum"></param>
        /// <returns></returns>
        private List<MaterialCodeReturnDTO> PerformUnpackOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
            decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum)
        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();
            string remark = $"拆包记录,原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库条码:{newBarcode}, å‡ºåº“数量:{actualOutboundQuantity},回库条码:{request.Barcode},回库数量:{stockDetail.StockQuantity - actualOutboundQuantity},操作者:{request.Operator}";
            List<Dt_MaterialCodeInfo> materialCodeInfos = CreateMaterialCodeInfos(stockDetail, newBarcode, actualOutboundQuantity, remark);
            List<Dt_MaterialCodeInfo> materialCodeInfos = CreateMaterialCodeInfos(stockDetail, newBarcode, actualOutboundQuantity, remark, taskNum, orderId, orderNo);
            List<MaterialCodeReturnDTO> returnDTOs = _mapper.Map<List<MaterialCodeReturnDTO>>(materialCodeInfos);
@@ -1489,9 +1907,11 @@
                Unit = stockDetail.Unit,
                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
                SupplyCode = stockDetail.SupplyCode,
                Creater = stockDetail.Creater,
                CreateDate = stockDetail.CreateDate,
                FactoryArea = stockDetail.FactoryArea,
                WarehouseCode = stockDetail.WarehouseCode,
                Remark = $"拆包前原始记录,原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
                Remark = $"拆包前原始记录,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
            };
            _stockDetailHistoryRepository.AddData(originalHistoryRecord);
@@ -1502,7 +1922,7 @@
                // æ›´æ–°åŽŸåº“å­˜æ˜Žç»†
                stockDetail.StockQuantity = remainingQuantity;
                //stockDetail.Barcode = newBarcode;
                stockDetail.Remark = $"拆包后更新,原条码:{request.Barcode},新数量:{remainingQuantity},操作者:{request.Operator}";
                stockDetail.Remark = $"拆包后更新,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},新数量:{remainingQuantity},操作者:{request.Operator}";
                _stockDetailRepository.UpdateData(stockDetail);
            }
@@ -1524,17 +1944,17 @@
                AfterQuantity = beforeQuantity - actualOutboundQuantity,
                SupplyCode = stockDetail.SupplyCode,
                WarehouseCode = stockDetail.WarehouseCode,
                Remark = $"拆包出库,原条码:{request.Barcode},新条码:{newBarcode},出库数量:{actualOutboundQuantity},剩余:{remainingQuantity},操作者:{request.Operator}"
                Remark = $"拆包出库,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},新条码:{newBarcode},出库数量:{actualOutboundQuantity},剩余:{remainingQuantity},操作者:{request.Operator}"
            };
            _stockChangeRepository.AddData(unpackChangeRecord);
            return returnDTOs;
            return (newBarcode, returnDTOs);
        }
        /// <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)
        {
            // ä¿å­˜åº“存明细到历史记录
@@ -1558,6 +1978,8 @@
                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
                SupplyCode = stockDetail.SupplyCode,
                FactoryArea = stockDetail.FactoryArea,
                Creater = stockDetail.Creater,
                CreateDate = stockDetail.CreateDate,
                WarehouseCode = stockDetail.WarehouseCode,
                Remark = $"出库完成删除,条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
            };
@@ -1612,7 +2034,7 @@
        /// <param name="afterQuantity"></param>
        /// <param name="remark"></param>
        /// <returns></returns>
        private List<Dt_MaterialCodeInfo> CreateMaterialCodeInfos(Dt_StockInfoDetail stockDetail, string newBarcode, decimal splitQuantity, string remark)
        private List<Dt_MaterialCodeInfo> CreateMaterialCodeInfos(Dt_StockInfoDetail stockDetail, string newBarcode, decimal splitQuantity, string remark, int taskNum, int orderId, string orderNo)
        {
            List<Dt_MaterialCodeInfo> materialCodeInfos = new List<Dt_MaterialCodeInfo>();
@@ -1633,7 +2055,11 @@
                SuplierCode = stockDetail.SupplyCode,
                Unit = stockDetail.Unit,
                Date = DateTime.Now.ToString("yyyy-MM-dd"),
                Remark = remark
                Remark = remark,
                WarehouseCode = stockDetail.WarehouseCode,
                OrderNo = orderNo,
                OrderId = orderId,
                ReturnStatus = 0
            };
            materialCodeInfos.Add(outMaterialCodeInfo);
@@ -1652,7 +2078,11 @@
                SuplierCode = stockDetail.SupplyCode,
                Unit = stockDetail.Unit,
                Date = DateTime.Now.ToString("yyyy-MM-dd"),
                Remark = remark
                Remark = remark,
                WarehouseCode = stockDetail.WarehouseCode,
                OrderNo = orderNo,
                OrderId = orderId,
                ReturnStatus = 0
            };
            materialCodeInfos.Add(returnMaterialCodeInfo);
@@ -1704,6 +2134,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 å–空箱
@@ -1712,7 +2172,13 @@
            WebResponseContent content = new WebResponseContent();
            try
            {
                var stock = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Includes(x=>x.Details).Where(x => x.PalletCode == palletCode).FirstAsync();
                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)
                {
@@ -1726,7 +2192,7 @@
                stockInfo_Hty.SourceId = stock.Id;
                stockInfo_Hty.OperateType = "取空箱";
                stockInfo_Hty.InsertTime = DateTime.Now;
                _unitOfWorkManage.BeginTran();
                await _outboundRepository.Db.InsertNav(stockInfo_Hty).IncludesAllFirstLayer().ExecuteCommandAsync();
                await _stockInfoRepository.DeleteDataByIdAsync(stock.Id);
@@ -1742,7 +2208,7 @@
        #endregion
        #region
        #region å›žåº“
        public async Task<WebResponseContent> ReturnToWarehouse(string palletCode, string OrderNo, string station)
        {
            WebResponseContent content = new WebResponseContent();
@@ -1758,21 +2224,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 = "";
                }
@@ -1788,15 +2253,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,
@@ -1806,11 +2276,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();
            }
@@ -1820,6 +2290,69 @@
            }
        }
        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
    }
}