From 5dfd83bd540c2e43af2e0449c246c79a22cb1296 Mon Sep 17 00:00:00 2001
From: heshaofeng <heshaofeng@hnkhzn.com>
Date: 星期二, 20 一月 2026 16:22:56 +0800
Subject: [PATCH] 1

---
 项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs | 2087 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 2,084 insertions(+), 3 deletions(-)

diff --git "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs" "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs"
index 0b2aae4..cc36450 100644
--- "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs"
+++ "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs"
@@ -1,9 +1,42 @@
-锘縰sing WIDESEA_IOutboundService;
+锘縰sing System.Reflection.Emit;
+using AutoMapper;
+using Dm.filter;
+using MailKit.Search;
+using Mapster;
+using Microsoft.IdentityModel.Tokens;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using Org.BouncyCastle.Asn1.Ocsp;
+using Org.BouncyCastle.Crypto;
+using SqlSugar;
+using WIDESEA_BasicService;
+using WIDESEA_Common.CommonEnum;
+using WIDESEA_Common.LocationEnum;
+using WIDESEA_Common.OrderEnum;
+using WIDESEA_Common.StockEnum;
+using WIDESEA_Common.TaskEnum;
+using WIDESEA_Core;
+using WIDESEA_Core.BaseRepository;
+using WIDESEA_Core.CodeConfigEnum;
+using WIDESEA_Core.Helper;
+using WIDESEA_DTO.Base;
+using WIDESEA_DTO.Basic;
+using WIDESEA_DTO.CalcOut;
+using WIDESEA_DTO.ReturnMES;
+using WIDESEA_IBasicService;
+using WIDESEA_IOutboundService;
+using WIDESEA_IRecordService;
+using WIDESEA_IStockService;
+using WIDESEA_Model.Models;
+using WIDESEA_Model.Models.Check;
+using static HslCommunication.Profinet.Knx.KnxCode;
 
 namespace WIDESEA_OutboundService
 {
-    public class OutboundService : IOutboundService
+    public partial class OutboundService : IOutboundService
     {
+        private readonly IMapper _mapper;
+        public IUnitOfWorkManage _unitOfWorkManage { get; }
 
         public IOutboundOrderDetailService OutboundOrderDetailService { get; }
 
@@ -11,11 +44,2059 @@
 
         public IOutStockLockInfoService OutboundStockLockInfoService { get; }
 
-        public OutboundService(IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService)
+        private readonly ISqlSugarClient Db;
+        private readonly IBasicService _basicService;
+
+
+        private readonly IRepository<Dt_OutboundOrderDetail> _detailRepository;
+        private readonly IRepository<Dt_OutboundOrder> _outboundRepository;
+        private readonly IRepository<Dt_OutStockLockInfo> _outboundLockInfoRepository;
+        private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
+        private readonly IRepository<Dt_StockInfoDetail> _stockDetailRepository;
+        private readonly IRepository<Dt_LocationInfo> _locationInfoRepository;
+        private readonly IRepository<Dt_StockQuantityChangeRecord> _stockChangeRepository;
+        private readonly IRepository<Dt_StockInfoDetail_Hty> _stockDetailHistoryRepository;
+        private readonly IFeedbackMesService _feedbackMesService;
+        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;
+
+        private Dictionary<string, string> stations = new Dictionary<string, string>
         {
+            {"2-1","2-9" },
+            {"3-1","3-9" },
+        };
+
+        private Dictionary<string, string> movestations = new Dictionary<string, string>
+        {
+            {"2-1","2-5" },
+            {"3-1","3-5" },
+        };
+
+        public 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)
+        {
+            _mapper = mapper;
+            _unitOfWorkManage = unitOfWorkManage;
+            Db = _unitOfWorkManage.GetDbClient();
             OutboundOrderDetailService = outboundOrderDetailService;
             OutboundOrderService = outboundOrderService;
             OutboundStockLockInfoService = outboundStockLockInfoService;
+            _detailRepository = detailRepository;
+            _outboundRepository = outboundRepository;
+            _outboundLockInfoRepository = outboundLockInfoRepository;
+            _stockInfoRepository = stockInfoRepository;
+            _stockDetailRepository = stockDetailRepository;
+            _locationInfoRepository = basicService.LocationInfoService.Repository;
+            _stockChangeRepository = stockChangeRepository;
+            _stockDetailHistoryRepository = stockDetailHistoryRepository;
+            _basicService = basicService;
+            _feedbackMesService = feedbackMesService;
+            _taskRepository = taskRepository;
+            _locationInfoService = locationInfoService;
+            _eSSApiService = eSSApiService;
+            _allocateOrderRepository = allocateOrderRepository;
+            _allocateMaterialInfoRepository = allocateMaterialInfoRepository;
         }
+
+        #region 鍑哄簱鍒嗛厤
+        /// <summary>
+        /// 鍒嗘嫞鍑哄簱鎿嶄綔
+        /// </summary>
+        /// <param name="request">鍒嗘嫞鍑哄簱璇锋眰</param>
+        /// <returns>鍒嗘嫞鍑哄簱鍝嶅簲</returns>
+        public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request)
+        {
+            WebResponseContent content = WebResponseContent.Instance;
+            PickingOutboundResponseDTO response = new PickingOutboundResponseDTO();
+            decimal totalNeedAllocate = 0; // 鎬婚渶姹傚垎閰嶉噺
+            decimal totalActualAllocate = 0; // 瀹為檯鎬诲垎閰嶉噺
+
+            try
+            {
+                _unitOfWorkManage.BeginTran();
+                // 1. 璁$畻鍑哄簱鏁伴噺閫昏緫
+                OutboundCalculationDTO calculationResult = CalcOutboundQuantity(request);
+                if (!calculationResult.CanOutbound)
+                {
+                    content = WebResponseContent.Instance.Error("鏃犳硶澶勭悊鎷h揣鍑哄簱锛�" + calculationResult.ErrorMessage);
+                    _unitOfWorkManage.RollbackTran();
+                    return content;
+                }
+                // 璁板綍鎬婚渶姹傛暟閲�
+                totalNeedAllocate = calculationResult.MaterielCalculations.Sum(x => x.UnallocatedQuantity);
+
+                // 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>();
+                List<Dt_Task> tasks = new List<Dt_Task>();
+
+                foreach (var materielCalc in calculationResult.MaterielCalculations)
+                {
+                    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? existLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0);
+                        if (existLockInfo != null)
+                        {
+                            existLockInfo = item;
+                            Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
+                            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;
+                            materielCalc.OutStockLockInfos.Add(item);
+                        }
+                        outStockLockInfos.Add(item);
+                    }
+
+                    // 澶勭悊浠诲姟
+                    foreach (var item in materielPickedDetails.Tasks)
+                    {
+                        if (tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode) == null)
+                            tasks.Add(item);
+                    }
+
+                    // 姹囨�诲垎鎷f槑缁�
+                    pickedDetails.AddRange(materielPickedDetails.PickedDetails);
+
+                    // 鎸夊疄闄呭垎閰嶉噺鏇存柊鍗曟嵁閿佸畾鏁伴噺
+                    decimal remainingToLock = actualAllocatedQuantity;
+                    foreach (var detail in materielCalc.Details)
+                    {
+                        if (remainingToLock <= 0) break;
+                        decimal maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity;
+                        if (maxLockableQty <= 0) continue;
+                        decimal currentLockQty = Math.Min(remainingToLock, maxLockableQty);
+                        detail.LockQuantity += currentLockQty;
+                        outboundOrderDetails.Add(detail);
+                        remainingToLock -= currentLockQty;
+                    }
+                }
+
+                // 3. 鎵归噺鏇存柊鐘舵�侊紙鍘熸湁閫昏緫涓嶅彉锛�
+                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());
+                }
+                //閲嶆鍗曚笉鎷i�夛紝鍘绘帀閿佸畾璁板綍鍥炲簱锛屽啀娆$粍鐩樻椂鎵i櫎鍘熸潯鐮�
+                if (outboundOrder.OrderType != InOrderTypeEnum.ReCheck.ObjToInt())
+                {
+                    UpdateOutStockLockInfo(outStockLockInfos);
+                }
+                
+                if (tasks.Any()) _taskRepository.AddData(tasks);
+
+                
+                    _unitOfWorkManage.CommitTran();
+
+                // 4. 鏋勯�犲搷搴旓細鍖哄垎銆屽叏閮ㄥ垎閰嶃�嶅拰銆岄儴鍒嗗垎閰嶃��
+                string responseMsg = totalActualAllocate == totalNeedAllocate
+                    ? "鍒嗘嫞浠诲姟鍒嗛厤鎴愬姛"
+                    : $"鍒嗘嫞浠诲姟鍒嗛厤瀹屾垚锛堝疄闄呭垎閰峽totalActualAllocate}锛岄渶姹倇totalNeedAllocate}锛屽簱瀛樹笉瓒抽儴鍒嗘湭鍒嗛厤锛�";
+                response.Success = true;
+                response.Message = responseMsg;
+                response.Tasks = tasks;
+                response.PickedDetails = pickedDetails;
+                content = WebResponseContent.Instance.OK(responseMsg, response);
+            }
+            catch (Exception ex)
+            {
+                _unitOfWorkManage.RollbackTran();
+                content = WebResponseContent.Instance.Error("澶勭悊鎷h揣鍑哄簱澶辫触锛�" + ex.Message);
+            }
+            return content;
+        }
+
+        /// <summary>
+        /// 璁$畻鍑哄簱鏁伴噺閫昏緫锛堝師鏈夐�昏緫涓嶅彉锛�
+        /// </summary>
+        public OutboundCalculationDTO CalcOutboundQuantity(PickingOutboundRequestDTO request)
+        {
+            OutboundCalculationDTO result = new OutboundCalculationDTO();
+            try
+            {
+                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
+                if (outboundOrder == null)
+                {
+                    result.CanOutbound = false;
+                    result.ErrorMessage = $"鍑哄簱鍗� {request.OrderNo} 涓嶅瓨鍦�";
+                    return result;
+                }
+
+                result.FactoryArea = outboundOrder.FactoryArea;
+                List<Dt_OutboundOrderDetail> selectedDetails = new List<Dt_OutboundOrderDetail>();
+                if (request.DetailIds == null || !request.DetailIds.Any())
+                {
+                    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())
+                {
+                    result.CanOutbound = false;
+                    result.ErrorMessage = $"鏈壘鍒伴�夋嫨鐨勫嚭搴撴槑缁嗕俊鎭�";
+                    return result;
+                }
+                if (selectedDetails.Any(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty))
+                {
+                    List<int> selectDetailIds = selectedDetails.Where(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty).Select(x => x.Id).ToList();
+                    result.CanOutbound = false;
+                    result.ErrorMessage = $"鍑哄簱鏄庣粏淇℃伅{string.Join(",", selectDetailIds)}宸插垎閰嶅畬鎴�";
+                    return result;
+                }
+
+                outboundOrder.Details = selectedDetails;
+                result.OutboundOrder = outboundOrder;
+                result.SelectedDetails = selectedDetails;
+
+                if (outboundOrder.IsBatch == 0 || request.DetailIds.Count != 1)
+                {
+                    result.MaterielCalculations = CalcMaterielOutboundQuantities(outboundOrder, selectedDetails.ToList());
+                }
+                else
+                {
+                    if (!request.OutboundQuantity.HasValue || request.OutboundQuantity.Value <= 0)
+                    {
+                        result.CanOutbound = false;
+                        result.ErrorMessage = "鍗曟槑缁嗗嚭搴撴椂蹇呴』鎸囧畾鍑哄簱鏁伴噺涓斿ぇ浜�0";
+                        return result;
+                    }
+
+                    decimal lockQuantity = selectedDetails.Sum(x => x.LockQuantity);
+                    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;
+                        result.ErrorMessage = $"鏈鍑哄簱鏁伴噺 {request.OutboundQuantity.Value} 瓒呰繃鍙嚭搴撴暟閲� {orderQuantity - lockQuantity - moveQuantity}";
+                        return result;
+                    }
+
+                    decimal inputQuantity = request.OutboundQuantity.Value;
+                    List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
+                    foreach (var item in selectedDetails)
+                    {
+                        inputQuantity -= (item.OrderQuantity - item.MoveQty - item.LockQuantity);
+                        outboundOrderDetails.Add(item);
+                        if (inputQuantity <= 0) break;
+                    }
+
+                    result.MaterielCalculations = new List<MaterielOutboundCalculationDTO>()
+                    {
+                        new MaterielOutboundCalculationDTO
+                        {
+                            MaterielCode = singleDetail.MaterielCode,
+                            MaterielName = singleDetail.MaterielName,
+                            BatchNo = singleDetail.BatchNo,
+                            SupplyCode = singleDetail.SupplyCode,
+                            WarehouseCode = singleDetail.WarehouseCode,
+                            TotalOrderQuantity = orderQuantity - moveQuantity,
+                            TotalOverOutQuantity = overQuantity,
+                            AssignedQuantity = lockQuantity,
+                            UnallocatedQuantity = request.OutboundQuantity.Value,
+                            MovedQuantity = moveQuantity,
+                            Details = outboundOrderDetails
+                        }
+                    };
+                    outboundOrder.Details = outboundOrderDetails;
+                }
+                result.CanOutbound = true;
+            }
+            catch (Exception ex)
+            {
+                result.CanOutbound = false;
+                result.ErrorMessage = ex.Message;
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 鎸夌墿鏂欏垎缁勮绠楀嚭搴撴暟閲忥紙鍘熸湁閫昏緫涓嶅彉锛�
+        /// </summary>
+        private List<MaterielOutboundCalculationDTO> CalcMaterielOutboundQuantities(Dt_OutboundOrder outboundOrder, List<Dt_OutboundOrderDetail> selectedDetails)
+        {
+            return selectedDetails
+                .GroupBy(x => new
+                {
+                    x.MaterielCode,
+                    x.BatchNo,
+                    x.SupplyCode,
+                    x.WarehouseCode
+                })
+                .Select(g => new MaterielOutboundCalculationDTO
+                {
+                    MaterielCode = g.Key.MaterielCode,
+                    BatchNo = g.Key.BatchNo,
+                    SupplyCode = g.Key.SupplyCode,
+                    WarehouseCode = g.Key.WarehouseCode,
+                    TotalOrderQuantity = g.Sum(x => x.OrderQuantity - x.MoveQty),
+                    TotalOverOutQuantity = g.Sum(x => x.OverOutQuantity),
+                    AssignedQuantity = g.Sum(x => x.LockQuantity),
+                    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)
+                })
+                .ToList();
+        }
+
+        /// <summary>
+        /// 澶勭悊鐗╂枡浠诲姟鐢熸垚锛堟牳蹇冿細鏈夊灏戝垎澶氬皯+绉婚櫎搴撳瓨涓嶈冻寮傚父锛�
+        /// </summary>
+        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_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
+            decimal remainingQuantity = materielCalc.UnallocatedQuantity;
+
+            // 1. 浼樺厛澶勭悊鎸囧畾搴撳瓨鏄庣粏
+            if (request.StockDetailIds?.Any() == true)
+            {
+                var specifiedResult = AllocateSpecifiedStockDetails(outboundOrder, materielCalc, request, factoryArea, remainingQuantity);
+                pickedDetails.AddRange(specifiedResult.PickedDetails);
+                lockInfoList.AddRange(specifiedResult.LockInfoList);
+                remainingQuantity -= specifiedResult.ActualAllocatedQuantity;
+
+                // ===== 鏍稿績淇锛氭寚瀹氬簱瀛樺垎閰嶅悗锛岀珛鍗崇敓鎴愪换鍔★紙鏃犺鍓╀綑閲忓灏戯級=====
+                if (specifiedResult.PickedDetails.Any()) // 鏈夋寚瀹氬簱瀛樺垎閰嶇粨鏋滃氨鐢熸垚浠诲姟
+                {
+                    var specifiedTasks = GenerateTasksForSpecifiedStock(specifiedResult.PickedDetails, request.OutboundTargetLocation);
+                    generatedTasks.AddRange(specifiedTasks); // 娣诲姞鎸囧畾搴撳瓨浠诲姟鍒版�讳换鍔″垪琛�
+                }
+
+                // 鍓╀綑閲�<=0鏃讹紝鐩存帴杩斿洖锛堝凡鐢熸垚鎸囧畾搴撳瓨浠诲姟锛�
+                if (remainingQuantity <= 0)
+                {
+                    return (pickedDetails, generatedTasks, lockInfoList);
+                }
+            }
+
+            // 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_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>();
+
+                foreach (var stock in stockQuery)
+                {
+                    if (autoAllocateQuantity <= 0) break;
+                    decimal availableQuantity = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0);
+                    if (availableQuantity <= 0) continue;
+
+                    // 鑷姩鍒嗛厤鏃舵煡璇㈡墭鐩樻�诲簱瀛橈紙鍘熸湁浼樺寲閫昏緫涓嶅彉锛�
+                    decimal palletMaterielTotalStock = _stockDetailRepository.QueryData(
+                        x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode
+                    ).Sum(x => x.StockQuantity);
+
+                    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);
+                    }
+                }
+
+                // 鐢熸垚鑷姩鍒嗛厤鐨勪换鍔★紙杩藉姞鍒癵eneratedTasks锛屼笉浼氶噸澶嶏級
+                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>
+        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.鎵嬪姩瑙i攣 || 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.鎵嬪姩瑙i攣));
+
+            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)
+        {
+            return new Dt_Task
+            {
+                CurrentAddress = stockInfo.LocationCode,
+                Grade = 0,
+                PalletCode = stockInfo.PalletCode,
+                NextAddress = outStation,
+                Roadway = "",
+                SourceAddress = stockInfo.LocationCode,
+                TargetAddress = outStation,
+                TaskStatus = TaskStatusEnum.New.ObjToInt(),
+                TaskType = taskType.ObjToInt(),
+                TaskNum = taskNum,
+                PalletType = stockInfo.PalletType,
+                WarehouseId = stockInfo.WarehouseId,
+            };
+        }
+        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;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        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);
+                _stockInfoRepository.UpdateData(stockInfos);
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        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);
+                _locationInfoRepository.UpdateData(locationInfos);
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        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
+            {
+                return false;
+            }
+        }
+        #endregion
+
+        #region 鏁寸鍑哄簱
+        public WebResponseContent CompleteOutboundWithPallet(OutboundCompletePalletRequestDTO request)
+        {
+            WebResponseContent content = WebResponseContent.Instance;
+
+            OutboundCompleteResponseDTO response = new();
+            try
+            {
+                // 1. 鏍规嵁鎵樼洏鍙锋煡鎵惧簱瀛樹俊鎭�
+                Dt_StockInfo stockInfo = _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletCode == request.PalletCode).Includes(x => x.Details).First();
+                if (stockInfo == null)
+                {
+                    response.Success = false;
+                    response.Message = $"鎵樼洏鍙� {request.PalletCode} 瀵瑰簲鐨勫簱瀛樹笉瀛樺湪";
+                    return WebResponseContent.Instance.Error(response.Message);
+                }
+
+                if (!stockInfo.Details.Any())
+                {
+                    response.Success = false;
+                    response.Message = $"鎵樼洏 {request.PalletCode} 瀵瑰簲鐨勫簱瀛樻槑缁嗕笉瀛樺湪";
+                    return WebResponseContent.Instance.Error(response.Message);
+                }
+
+                // 2. 鏌ユ壘鍑哄簱鍗曚俊鎭�
+                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
+                if (outboundOrder == null)
+                {
+                    response.Success = false;
+                    response.Message = $"鍑哄簱鍗� {request.OrderNo} 涓嶅瓨鍦�";
+                    return WebResponseContent.Instance.Error(response.Message);
+                }
+
+                Dt_StockInfoDetail stockInfoDetail = stockInfo.Details.First();
+
+                // 3. 鏌ユ壘閿佸畾璁板綍
+                Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
+                    x.OrderNo == request.OrderNo &&
+                    x.StockId == stockInfo.Id &&
+                    x.MaterielCode == stockInfoDetail.MaterielCode &&
+                    x.PalletCode == stockInfo.PalletCode);
+
+                if (lockInfo == null || lockInfo.AssignQuantity <= 0)
+                {
+                    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);
+                }
+
+                // 鎵惧嚭宸插垎閰嶇殑璁㈠崟鏄庣粏Id
+                List<int> detailIds = new List<int>();
+                string[] ids = lockInfo.OrderDetailIds.Split(",");
+                foreach (string id in ids)
+                {
+                    if (int.TryParse(id, out int detailId))
+                    {
+                        detailIds.Add(detailId);
+                    }
+                }
+                // 4. 鏌ユ壘鍑哄簱鍗曟槑缁嗕俊鎭�
+                List<Dt_OutboundOrderDetail> outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockInfoDetail, detailIds);
+                if (!outboundOrderDetails.Any())
+                {
+                    response.Success = false;
+                    response.Message = $"鏈壘鍒板尮閰嶇殑鍑哄簱鍗曟槑缁嗭紝鐗╂枡锛歿stockInfoDetail.MaterielCode}锛屾壒娆★細{stockInfoDetail.BatchNo}";
+                    return WebResponseContent.Instance.Error(response.Message);
+                }
+
+                decimal totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
+
+                // 5. 璁$畻瀹為檯鍑哄簱閲�
+                decimal actualOutboundQuantity = CalculateActualOutboundQuantity(stockInfo.Details, outboundOrderDetails, lockInfo);// 闇�鍑哄簱閲�
+
+                if (actualOutboundQuantity <= 0)
+                {
+                    decimal totalAllocatedQuantity = lockInfo.AllocatedQuantity;
+                    decimal availableOutboundQuantity = lockInfo.AssignQuantity - totalAllocatedQuantity;
+                    decimal detailRemainingQuantity = outboundOrderDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);
+
+                    response.Success = false;
+                    response.Message = $"鏃犳硶鍑哄簱锛屾墭鐩樺彿锛歿request.PalletCode}锛屽簱瀛橀噺锛歿totalStockQuantity}锛屽凡鍑哄簱锛歿totalAllocatedQuantity}锛屽垎閰嶉噺锛歿lockInfo.AssignQuantity}锛屾槑缁嗗墿浣欙細{detailRemainingQuantity}";
+                    return WebResponseContent.Instance.Error(response.Message);
+                }
+
+                if (lockInfo.AssignQuantity != totalStockQuantity)
+                {
+                    response.Success = false;
+                    response.Message = $"鏃犳硶鍑哄簱锛屾墭鐩樺彿锛歿request.PalletCode}锛屽簱瀛橀噺锛歿totalStockQuantity}锛屽垎閰嶉噺锛歿lockInfo.AssignQuantity}";
+                    return WebResponseContent.Instance.Error(response.Message);
+                }
+
+                // 6. 寮�鍚簨鍔�
+                _unitOfWorkManage.BeginTran();
+                try
+                {
+                    // 鏁寸鍑哄簱鏃犻渶鎷嗗寘
+                    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();
+                    foreach (var item in outboundOrderDetails)
+                    {
+                        if (allocatedQuantity <= 0) break;
+
+
+                        //if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity)
+                        //{
+                        //    item.OverOutQuantity += allocatedQuantity;
+                        //    allocatedQuantity = 0;
+                        //}
+                        //else
+                        //{
+                        //    allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity);
+                        //    item.OverOutQuantity = item.OrderQuantity - item.MoveQty;
+                        //}
+                        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 (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 = currentResult.ToQuantity,
+                                    SupplyCode = stockDetail?.SupplyCode ?? "",
+                                    BatchNo = stockDetail?.BatchNo ?? "",
+                                    Unit = currentResult.ToUnit ?? ""
+                                };
+
+                                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 = currentResult.ToQuantity,
+                                    SupplyCode = stockDetail?.SupplyCode ?? "",
+                                    BatchNo = stockDetail?.BatchNo ?? "",
+                                    Unit = currentResult.ToUnit ?? ""
+                                };
+                                stockDetail.OutboundQuantity += itemQuantity;
+                                barcodesList.Add(barcodes);
+                                break;
+                            }
+                        }
+
+                        decimal barcodeQuantity = allocatedQuantity;
+
+                        if (item.LockQuantity - item.OverOutQuantity >= allocatedQuantity)
+                        {
+                            item.OverOutQuantity += allocatedQuantity;
+                            item.CurrentDeliveryQty += allocatedQuantity;
+                            allocatedQuantity = 0;
+                        }
+                        else
+                        {
+                            barcodeQuantity = item.LockQuantity - item.OverOutQuantity;
+                            allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
+                            item.OverOutQuantity = item.LockQuantity;
+                            item.CurrentDeliveryQty = item.LockQuantity;
+                        }
+
+                        updateDetails.Add(item);
+
+
+                        if (!string.IsNullOrEmpty(item.ReturnJsonData))
+                        {
+                            barcodesList.AddRange(JsonConvert.DeserializeObject<List<Barcodes>>(item.ReturnJsonData) ?? new List<Barcodes>());
+                        }
+
+                        JsonSerializerSettings settings = new JsonSerializerSettings
+                        {
+                            ContractResolver = new CamelCasePropertyNamesContractResolver()
+                        };
+                        item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings);
+                        //閲嶆嫞鍑哄簱涓嶉渶瑕佸洖浼�
+                        if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt())
+                        {
+                            item.ReturnJsonData = "";
+                        }
+                    }
+
+                    lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
+
+                    if (lockInfo.SortedQuantity == lockInfo.AssignQuantity)
+                    {
+                        _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.鑷姩瀹屾垚);
+                    }
+                    else
+                    {
+                        // 鏇存柊閿佸畾璁板綍
+                        _outboundLockInfoRepository.UpdateData(lockInfo);
+                    }
+
+                    // 鏇存柊鍑哄簱鍗曟槑缁嗙殑宸插嚭搴撴暟閲�
+                    _detailRepository.UpdateData(updateDetails);
+
+                    // 鏇存柊閿佸畾璁板綍鐨勭疮璁″凡鍑哄簱鏁伴噺锛堥渶瑕佹洿鏂拌鎵樼洏璇ョ墿鏂欑殑鎵�鏈夌浉鍏宠褰曪級
+                    //UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity);
+
+                    // 鎻愪氦浜嬪姟
+                    _unitOfWorkManage.CommitTran();
+
+                    response.Success = true;
+                    response.Message = "鍑哄簱瀹屾垚";
+                    response.UpdatedDetails = updateDetails;
+                    
+                    // 妫�鏌ュ嚭搴撳崟鏄惁瀹屾垚
+                    if (CheckOutboundOrderCompleted(request.OrderNo))
+                    {
+                        UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.鍑哄簱瀹屾垚.ObjToInt());
+
+                        if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt()&& outboundOrder.OrderType!= InOrderTypeEnum.ReCheck.ObjToInt())
+                        {
+                            _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
+                        }
+
+                    }
+                }
+                catch (Exception ex)
+                {
+                    _unitOfWorkManage.RollbackTran();
+                    response.Success = false;
+                    response.Message = $"鍑哄簱澶勭悊澶辫触锛歿ex.Message}";
+                    return WebResponseContent.Instance.Error(ex.Message);
+                }
+
+                content = WebResponseContent.Instance.OK(data: response);
+            }
+            catch (Exception ex)
+            {
+                content = WebResponseContent.Instance.Error("澶勭悊鍑哄簱瀹屾垚澶辫触锛�" + ex.Message);
+            }
+            return content;
+        }
+
+        /// <summary>
+        /// 璁$畻瀹為檯鍑哄簱鏁伴噺
+        /// </summary>
+        private decimal CalculateActualOutboundQuantity(List<Dt_StockInfoDetail> stockDetails, List<Dt_OutboundOrderDetail> outboundDetails, Dt_OutStockLockInfo lockInfo)
+        {
+            decimal availableOutboundQuantity = lockInfo.AssignQuantity;
+            decimal detailRemainingQuantity = outboundDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);//outboundDetail.OrderQuantity - outboundDetail.OverOutQuantity;
+
+            return Math.Min(
+                Math.Min(availableOutboundQuantity, detailRemainingQuantity),
+                stockDetails.Sum(x => x.StockQuantity));
+        }
+
+        /// <summary>
+        /// 鎵ц瀹屾暣鍑哄簱鎿嶄綔锛堜笉鎷嗗寘锛�
+        /// </summary>
+        private void PerformFullOutboundOperation(Dt_StockInfo stockInfo, OutboundCompletePalletRequestDTO request, int taskNum)
+        {
+            List<Dt_StockInfoDetail_Hty> historyRecords = new List<Dt_StockInfoDetail_Hty>();
+            List<Dt_StockQuantityChangeRecord> changeRecords = new List<Dt_StockQuantityChangeRecord>();
+            foreach (var item in stockInfo.Details)
+            {
+                // 淇濆瓨搴撳瓨鏄庣粏鍒板巻鍙茶褰�
+                Dt_StockInfoDetail_Hty historyRecord = new Dt_StockInfoDetail_Hty
+                {
+                    SourceId = item.Id,
+                    OperateType = "鍑哄簱瀹屾垚",
+                    InsertTime = DateTime.Now,
+                    StockId = item.StockId,
+                    MaterielCode = item.MaterielCode,
+                    MaterielName = item.MaterielName,
+                    OrderNo = item.OrderNo,
+                    BatchNo = item.BatchNo,
+                    ProductionDate = item.ProductionDate,
+                    EffectiveDate = item.EffectiveDate,
+                    SerialNumber = item.SerialNumber,
+                    StockQuantity = item.StockQuantity,
+                    OutboundQuantity = item.StockQuantity,
+                    Status = item.Status,
+                    Unit = item.Unit,
+                    InboundOrderRowNo = item.InboundOrderRowNo,
+                    SupplyCode = item.SupplyCode,
+                    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);
+
+                // 璁板綍搴撳瓨鍙樺姩
+                Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord
+                {
+                    StockDetailId = item.Id,
+                    PalleCode = stockInfo.PalletCode,
+                    MaterielCode = item.MaterielCode,
+                    MaterielName = item.MaterielName,
+                    BatchNo = item.BatchNo,
+                    OriginalSerilNumber = item.Barcode,
+                    NewSerilNumber = "",
+                    OrderNo = request.OrderNo,
+                    TaskNum = taskNum,
+                    ChangeType = (int)StockChangeTypeEnum.Outbound,
+                    ChangeQuantity = -item.StockQuantity,
+                    BeforeQuantity = item.StockQuantity,
+                    AfterQuantity = 0,
+                    SupplyCode = item.SupplyCode,
+                    WarehouseCode = item.WarehouseCode,
+                    Remark = $"鏁寸鍑哄簱瀹屾垚鍒犻櫎搴撳瓨鏄庣粏锛屾潯鐮侊細{request.PalletCode}锛屽嚭搴撴暟閲忥細{item.StockQuantity}锛屾搷浣滆�咃細{request.Operator}"
+                };
+                changeRecords.Add(changeRecord);
+            }
+            _stockDetailHistoryRepository.AddData(historyRecords);
+
+            // 鍒犻櫎搴撳瓨鏄庣粏璁板綍
+            _stockDetailRepository.DeleteData(stockInfo.Details);
+            _stockChangeRepository.AddData(changeRecords);
+        }
+        #endregion
+
+        #region 鎷i��
+        /// <summary>
+        /// 鍑哄簱瀹屾垚澶勭悊锛堟壂鎻忔潯鐮佹墸鍑忓簱瀛橈級
+        /// </summary>
+        /// <param name="request">鍑哄簱瀹屾垚璇锋眰</param>
+        /// <returns>鍑哄簱瀹屾垚鍝嶅簲</returns>
+        public WebResponseContent CompleteOutboundWithBarcode(OutboundCompleteRequestDTO request)
+        {
+            WebResponseContent content = WebResponseContent.Instance;
+
+            OutboundCompleteResponseDTO response = new();
+            try
+            {
+                // 1. 鏍规嵁鎵樼洏鍙锋煡鎵惧簱瀛樹俊鎭�
+                Dt_StockInfo stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == request.PalletCode);
+                if (stockInfo == null)
+                {
+                    response.Success = false;
+                    response.Message = $"鎵樼洏鍙� {request.PalletCode} 瀵瑰簲鐨勫簱瀛樹笉瀛樺湪";
+                    return WebResponseContent.Instance.Error($"鎵樼洏鍙� {request.PalletCode} 瀵瑰簲鐨勫簱瀛樹笉瀛樺湪");
+                }
+
+                // 2. 鏍规嵁鏉$爜鏌ユ壘搴撳瓨鏄庣粏
+                Dt_StockInfoDetail stockDetail = _stockDetailRepository.QueryFirst(x => x.Barcode == request.Barcode);
+                if (stockDetail == null)
+                {
+                    response.Success = false;
+                    response.Message = $"鏉$爜 {request.Barcode} 瀵瑰簲鐨勫簱瀛樻槑缁嗕笉瀛樺湪";
+                    return WebResponseContent.Instance.Error($"鏉$爜 {request.Barcode} 瀵瑰簲鐨勫簱瀛樻槑缁嗕笉瀛樺湪");
+                }
+
+                // 3. 楠岃瘉搴撳瓨鏄庣粏涓庢墭鐩樻槸鍚﹀尮閰�
+                if (stockDetail.StockId != stockInfo.Id)
+                {
+                    response.Success = false;
+                    response.Message = $"鏉$爜 {request.Barcode} 涓嶅睘浜庢墭鐩樺彿 {request.PalletCode} 鐨勫簱瀛樻槑缁�";
+                    return WebResponseContent.Instance.Error($"鏉$爜 {request.Barcode} 涓嶅睘浜庢墭鐩樺彿 {request.PalletCode} 鐨勫簱瀛樻槑缁�");
+                }
+
+                // 4. 鏌ユ壘鍑哄簱鍗曚俊鎭�
+                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
+                if (outboundOrder == null)
+                {
+                    response.Success = false;
+                    response.Message = $"鍑哄簱鍗� {request.OrderNo} 涓嶅瓨鍦�";
+                    return WebResponseContent.Instance.Error($"鍑哄簱鍗� {request.OrderNo} 涓嶅瓨鍦�");
+                }
+
+                // 5. 鏌ユ壘閿佸畾璁板綍
+                Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
+                    x.OrderNo == request.OrderNo &&
+                    x.StockId == stockInfo.Id &&
+                    x.MaterielCode == stockDetail.MaterielCode &&
+                    x.PalletCode == stockInfo.PalletCode);
+
+                if (lockInfo == null || lockInfo.AssignQuantity <= 0)
+                {
+                    response.Success = false;
+                    response.Message = $"璇ュ簱瀛樻病鏈夊垎閰嶅嚭搴撻噺锛屾潯鐮侊細{request.Barcode}";
+                    return WebResponseContent.Instance.Error($"璇ュ簱瀛樻病鏈夊垎閰嶅嚭搴撻噺锛屾潯鐮侊細{request.Barcode}");
+                }
+
+                // 鎵惧嚭宸插垎閰嶇殑璁㈠崟鏄庣粏Id
+                List<int> detailIds = new List<int>();
+                string[] ids = lockInfo.OrderDetailIds.Split(",");
+                foreach (string id in ids)
+                {
+                    if (int.TryParse(id, out int detailId))
+                    {
+                        detailIds.Add(detailId);
+                    }
+                }
+                // 6. 鏌ユ壘鍑哄簱鍗曟槑缁嗕俊鎭�
+                List<Dt_OutboundOrderDetail> outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockDetail, detailIds);
+                if (!outboundOrderDetails.Any())
+                {
+                    response.Success = false;
+                    response.Message = $"鏈壘鍒板尮閰嶇殑鍑哄簱鍗曟槑缁嗭紝鐗╂枡锛歿stockDetail.MaterielCode}锛屾壒娆★細{stockDetail.BatchNo}";
+                    return WebResponseContent.Instance.Error($"鏈壘鍒板尮閰嶇殑鍑哄簱鍗曟槑缁嗭紝鐗╂枡锛歿stockDetail.MaterielCode}锛屾壒娆★細{stockDetail.BatchNo}");
+                }
+
+                // 7. 璁$畻瀹為檯鍑哄簱閲�
+                decimal actualOutboundQuantity = CalculateActualOutboundQuantity(stockDetail, outboundOrderDetails, lockInfo);// 闇�鍑哄簱閲�
+
+                if (actualOutboundQuantity <= 0)
+                {
+                    decimal totalAllocatedQuantity = lockInfo.AllocatedQuantity;
+                    decimal availableOutboundQuantity = lockInfo.AssignQuantity - totalAllocatedQuantity;
+                    decimal detailRemainingQuantity = outboundOrderDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);
+
+                    response.Success = false;
+                    response.Message = $"鏃犳硶鍑哄簱锛屾潯鐮侊細{request.Barcode}锛屽簱瀛橈細{stockDetail.StockQuantity}锛屽凡鍑哄簱锛歿totalAllocatedQuantity}锛屽垎閰嶉噺锛歿lockInfo.AssignQuantity}锛屾槑缁嗗墿浣欙細{detailRemainingQuantity}";
+                    return WebResponseContent.Instance.Error($"鏃犳硶鍑哄簱锛屾潯鐮侊細{request.Barcode}锛屽簱瀛橈細{stockDetail.StockQuantity}锛屽凡鍑哄簱锛歿totalAllocatedQuantity}锛屽垎閰嶉噺锛歿lockInfo.AssignQuantity}锛屾槑缁嗗墿浣欙細{detailRemainingQuantity}");
+                }
+
+                //if (actualOutboundQuantity + lockInfo.SortedQuantity > lockInfo.AssignQuantity)
+                //{
+                //    response.Success = false;
+                //    response.Message = $"鏃犳硶鍑哄簱锛屾潯鐮侊細{request.Barcode}锛屽簱瀛橈細{stockDetail.StockQuantity}锛屽嚭搴撻噺{actualOutboundQuantity + lockInfo.SortedQuantity}澶т簬鍒嗛厤閲弡lockInfo.AssignQuantity}";
+                //    return WebResponseContent.Instance.Error($"鏃犳硶鍑哄簱锛屾潯鐮侊細{request.Barcode}锛屽簱瀛橈細{stockDetail.StockQuantity}锛屽嚭搴撻噺{actualOutboundQuantity + lockInfo.SortedQuantity}澶т簬鍒嗛厤閲弡lockInfo.AssignQuantity}");
+                //}
+
+                // 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)
+                    {
+                        (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;
+                    List<Dt_OutboundOrderDetail> updateDetails = new();
+                    foreach (var item in outboundOrderDetails)
+                    {
+                        if (allocatedQuantity <= 0) break;
+
+                        //if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity)
+                        //{
+                        //    item.OverOutQuantity += allocatedQuantity;
+                        //    allocatedQuantity = 0;
+                        //}
+                        //else
+                        //{
+                        //    allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity);
+                        //    item.OverOutQuantity = item.OrderQuantity - item.MoveQty;
+                        //}
+
+                        decimal barcodeQuantity = allocatedQuantity;
+
+                        if (item.LockQuantity - item.OverOutQuantity >= allocatedQuantity)
+                        {
+                            item.OverOutQuantity += allocatedQuantity;
+                            item.CurrentDeliveryQty += allocatedQuantity;
+                            allocatedQuantity = 0;
+                        }
+                        else
+                        {
+                            barcodeQuantity = item.LockQuantity - item.OverOutQuantity;
+                            allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
+                            item.OverOutQuantity = item.LockQuantity;
+                            item.CurrentDeliveryQty = item.LockQuantity;
+                        }
+
+                        updateDetails.Add(item);
+
+                        List<Barcodes> barcodesList = new List<Barcodes>();
+                        UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, barcodeQuantity);
+
+                        Barcodes barcodes = new Barcodes
+                        {
+                            Barcode = isUnpacked ? newBarcode : stockDetail?.Barcode,
+                            Qty = currentResult.ToQuantity,
+                            SupplyCode = stockDetail?.SupplyCode ?? "",
+                            BatchNo = stockDetail?.BatchNo ?? "",
+                            Unit = currentResult.ToUnit ?? ""
+                        };
+                        if (!string.IsNullOrEmpty(item.ReturnJsonData))
+                        {
+                            barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(item.ReturnJsonData) ?? new List<Barcodes>();
+                        }
+                        barcodesList.Add(barcodes);
+                        JsonSerializerSettings settings = new JsonSerializerSettings
+                        {
+                            ContractResolver = new CamelCasePropertyNamesContractResolver()
+                        };
+                        item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings);
+                    }
+
+                    lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
+
+                    if (lockInfo.SortedQuantity >= lockInfo.AssignQuantity)
+                    {
+                        _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.鑷姩瀹屾垚);
+                    }
+                    else
+                    {
+                        // 鏇存柊閿佸畾璁板綍
+                        _outboundLockInfoRepository.UpdateData(lockInfo);
+                    }
+
+                    // 鏇存柊鍑哄簱鍗曟槑缁嗙殑宸插嚭搴撴暟閲�
+                    _detailRepository.UpdateData(updateDetails);
+
+                    // 鏇存柊閿佸畾璁板綍鐨勭疮璁″凡鍑哄簱鏁伴噺锛堥渶瑕佹洿鏂拌鎵樼洏璇ョ墿鏂欑殑鎵�鏈夌浉鍏宠褰曪級
+                    //UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity);
+
+                    // 鎻愪氦浜嬪姟
+                    _unitOfWorkManage.CommitTran();
+
+                    // 鏋勫缓杩斿洖淇℃伅
+                    ScannedStockDetailDTO scannedDetail = new ScannedStockDetailDTO
+                    {
+                        StockDetailId = stockDetail.Id,
+                        PalletCode = stockInfo.PalletCode,
+                        MaterielCode = stockDetail.MaterielCode,
+                        MaterielName = stockDetail.MaterielName,
+                        BatchNo = stockDetail.BatchNo,
+                        OriginalBarcode = request.Barcode,
+                        BeforeQuantity = beforeQuantity,
+                        AfterQuantity = isUnpacked ? actualOutboundQuantity : 0,
+                        ChangeQuantity = -actualOutboundQuantity,
+                        IsUnpacked = isUnpacked,
+                        MaterialCodes = returnDTOs
+                    };
+
+                    response.Success = true;
+                    response.Message = "鍑哄簱瀹屾垚";
+                    response.ScannedDetail = scannedDetail;
+                    response.UpdatedDetails = updateDetails;
+
+                    if (!string.IsNullOrEmpty(newBarcode))
+                    {
+                        // 鐗╂枡鏂版潯鐮佸洖浼�
+                        _feedbackMesService.BarcodeFeedback(newBarcode);
+                    }
+
+                    if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails.First()))
+                    {
+                        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());
+
+                        if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt() && outboundOrder.CreateType!=OrderCreateTypeEnum.CreateInSystem.ObjToInt())
+                        {
+                            _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    _unitOfWorkManage.RollbackTran();
+                    response.Success = false;
+                    response.Message = $"鍑哄簱澶勭悊澶辫触锛歿ex.Message}";
+                    return WebResponseContent.Instance.Error(ex.Message);
+                }
+
+                content = WebResponseContent.Instance.OK(data: response);
+            }
+            catch (Exception ex)
+            {
+                content = WebResponseContent.Instance.Error("澶勭悊鍑哄簱瀹屾垚澶辫触锛�" + ex.Message);
+            }
+            return content;
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="orderId"></param>
+        /// <param name="stockDetail"></param>
+        /// <returns></returns>
+        private List<Dt_OutboundOrderDetail> FindMatchingOutboundDetails(int orderId, Dt_StockInfoDetail stockDetail, List<int> detailIds)
+        {
+            List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x =>
+                x.OrderId == orderId &&
+                x.MaterielCode == stockDetail.MaterielCode && x.OrderQuantity - x.MoveQty > x.OverOutQuantity && detailIds.Contains(x.Id));
+
+            // 绮剧‘鍖归厤锛氬鐞唍ull鍊肩殑鎵规銆佷緵搴斿晢銆佷粨搴�
+            List<Dt_OutboundOrderDetail> exactMatches = details.Where(x =>
+                (string.IsNullOrEmpty(x.BatchNo)) || x.BatchNo == stockDetail.BatchNo
+            ).Where(x =>
+                (string.IsNullOrEmpty(x.SupplyCode)) || x.SupplyCode == stockDetail.SupplyCode
+            ).Where(x =>
+                (string.IsNullOrEmpty(x.WarehouseCode)) || x.WarehouseCode == stockDetail.WarehouseCode
+            ).ToList();
+
+            return exactMatches;
+        }
+
+        /// <summary>
+        /// 璁$畻瀹為檯鍑哄簱鏁伴噺
+        /// </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;
+
+            return Math.Min(
+                Math.Min(availableOutboundQuantity, detailRemainingQuantity),
+                stockDetail.StockQuantity);
+        }
+
+        /// <summary>
+        /// 鎵ц鎷嗗寘鎿嶄綔
+        /// </summary>
+        /// <param name="stockDetail"></param>
+        /// <param name="stockInfo"></param>
+        /// <param name="actualOutboundQuantity"></param>
+        /// <param name="request"></param>
+        /// <param name="beforeQuantity"></param>
+        /// <param name="taskNum"></param>
+        /// <returns></returns>
+        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, taskNum, orderId, orderNo);
+
+            List<MaterialCodeReturnDTO> returnDTOs = _mapper.Map<List<MaterialCodeReturnDTO>>(materialCodeInfos);
+
+            // 淇濆瓨鍘熷搴撳瓨鏄庣粏鍒板巻鍙茶褰�
+            Dt_StockInfoDetail_Hty originalHistoryRecord = new Dt_StockInfoDetail_Hty
+            {
+                SourceId = stockDetail.Id,
+                OperateType = "鎷嗗寘-鍘熷璁板綍",
+                InsertTime = DateTime.Now,
+                StockId = stockDetail.StockId,
+                MaterielCode = stockDetail.MaterielCode,
+                MaterielName = stockDetail.MaterielName,
+                OrderNo = stockDetail.OrderNo,
+                BatchNo = stockDetail.BatchNo,
+                ProductionDate = stockDetail.ProductionDate,
+                EffectiveDate = stockDetail.EffectiveDate,
+                SerialNumber = stockDetail.SerialNumber,
+                StockQuantity = stockDetail.StockQuantity,
+                OutboundQuantity = stockDetail.OutboundQuantity,
+                Status = stockDetail.Status,
+                Unit = stockDetail.Unit,
+                InboundOrderRowNo = stockDetail.InboundOrderRowNo,
+                SupplyCode = stockDetail.SupplyCode,
+                Creater = stockDetail.Creater,
+                CreateDate = stockDetail.CreateDate,
+                FactoryArea = stockDetail.FactoryArea,
+                WarehouseCode = stockDetail.WarehouseCode,
+                Remark = $"鎷嗗寘鍓嶅師濮嬭褰曪紝鍑哄簱鍗曞彿锛歿orderNo}锛屽嚭搴撳崟涓婚敭锛歿orderId}锛屽師鏉$爜锛歿request.Barcode}锛屽師鏁伴噺锛歿stockDetail.StockQuantity}锛屽嚭搴撴暟閲忥細{actualOutboundQuantity}锛屾搷浣滆�咃細{request.Operator}"
+            };
+            _stockDetailHistoryRepository.AddData(originalHistoryRecord);
+
+            // 淇濆瓨鍓╀綑閮ㄥ垎鍒板巻鍙茶褰�
+            decimal remainingQuantity = stockDetail.StockQuantity - actualOutboundQuantity;
+            if (remainingQuantity > 0)
+            {
+                // 鏇存柊鍘熷簱瀛樻槑缁�
+                stockDetail.StockQuantity = remainingQuantity;
+                //stockDetail.Barcode = newBarcode;
+                stockDetail.Remark = $"鎷嗗寘鍚庢洿鏂帮紝鍑哄簱鍗曞彿锛歿orderNo}锛屽嚭搴撳崟涓婚敭锛歿orderId}锛屽師鏉$爜锛歿request.Barcode}锛屾柊鏁伴噺锛歿remainingQuantity}锛屾搷浣滆�咃細{request.Operator}";
+                _stockDetailRepository.UpdateData(stockDetail);
+            }
+
+            // 璁板綍鎷嗗寘鍙樺姩
+            Dt_StockQuantityChangeRecord unpackChangeRecord = new Dt_StockQuantityChangeRecord
+            {
+                StockDetailId = stockDetail.Id,
+                PalleCode = stockInfo.PalletCode,
+                MaterielCode = stockDetail.MaterielCode,
+                MaterielName = stockDetail.MaterielName,
+                BatchNo = stockDetail.BatchNo,
+                OriginalSerilNumber = request.Barcode,
+                NewSerilNumber = newBarcode,
+                OrderNo = request.OrderNo,
+                TaskNum = taskNum,
+                ChangeType = (int)StockChangeTypeEnum.Outbound,
+                ChangeQuantity = -actualOutboundQuantity,
+                BeforeQuantity = beforeQuantity,
+                AfterQuantity = beforeQuantity - actualOutboundQuantity,
+                SupplyCode = stockDetail.SupplyCode,
+                WarehouseCode = stockDetail.WarehouseCode,
+                Remark = $"鎷嗗寘鍑哄簱锛屽嚭搴撳崟鍙凤細{orderNo}锛屽嚭搴撳崟涓婚敭锛歿orderId}锛屽師鏉$爜锛歿request.Barcode}锛屾柊鏉$爜锛歿newBarcode}锛屽嚭搴撴暟閲忥細{actualOutboundQuantity}锛屽墿浣欙細{remainingQuantity}锛屾搷浣滆�咃細{request.Operator}"
+            };
+            _stockChangeRepository.AddData(unpackChangeRecord);
+
+            return (newBarcode, returnDTOs);
+        }
+
+        /// <summary>
+        /// 鎵ц瀹屾暣鍑哄簱鎿嶄綔锛堜笉鎷嗗寘锛�
+        /// </summary>
+        public void PerformFullOutboundOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
+            decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum)
+        {
+            // 淇濆瓨搴撳瓨鏄庣粏鍒板巻鍙茶褰�
+            Dt_StockInfoDetail_Hty historyRecord = new Dt_StockInfoDetail_Hty
+            {
+                SourceId = stockDetail.Id,
+                OperateType = "鍑哄簱瀹屾垚",
+                InsertTime = DateTime.Now,
+                StockId = stockDetail.StockId,
+                MaterielCode = stockDetail.MaterielCode,
+                MaterielName = stockDetail.MaterielName,
+                OrderNo = stockDetail.OrderNo,
+                BatchNo = stockDetail.BatchNo,
+                ProductionDate = stockDetail.ProductionDate,
+                EffectiveDate = stockDetail.EffectiveDate,
+                SerialNumber = stockDetail.SerialNumber,
+                StockQuantity = stockDetail.StockQuantity,
+                OutboundQuantity = stockDetail.OutboundQuantity + actualOutboundQuantity,
+                Status = stockDetail.Status,
+                Unit = stockDetail.Unit,
+                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}"
+            };
+            _stockDetailHistoryRepository.AddData(historyRecord);
+
+            // 鍒犻櫎搴撳瓨鏄庣粏璁板綍
+            _stockDetailRepository.DeleteData(stockDetail);
+
+            // 璁板綍搴撳瓨鍙樺姩
+            Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord
+            {
+                StockDetailId = stockDetail.Id,
+                PalleCode = stockInfo.PalletCode,
+                MaterielCode = stockDetail.MaterielCode,
+                MaterielName = stockDetail.MaterielName,
+                BatchNo = stockDetail.BatchNo,
+                OriginalSerilNumber = request.Barcode,
+                NewSerilNumber = "",
+                OrderNo = request.OrderNo,
+                TaskNum = taskNum,
+                ChangeType = (int)StockChangeTypeEnum.Outbound,
+                ChangeQuantity = -actualOutboundQuantity,
+                BeforeQuantity = beforeQuantity,
+                AfterQuantity = 0,
+                SupplyCode = stockDetail.SupplyCode,
+                WarehouseCode = stockDetail.WarehouseCode,
+                Remark = $"鍑哄簱瀹屾垚鍒犻櫎搴撳瓨鏄庣粏锛屾潯鐮侊細{request.Barcode}锛屽嚭搴撴暟閲忥細{actualOutboundQuantity}锛屾搷浣滆�咃細{request.Operator}"
+            };
+            _stockChangeRepository.AddData(changeRecord);
+        }
+
+        /// <summary>
+        /// 鐢熸垚鏂扮殑鏉$爜
+        /// </summary>
+        /// <returns>鏂版潯鐮�</returns>
+        private string GenerateNewBarcode()
+        {
+            // 浣跨敤鏃堕棿鎴冲拰闅忔満鏁扮敓鎴愬敮涓�鏉$爜
+            string newBarcode = string.Empty;
+
+            newBarcode = _basicService.CreateCodeByRule(RuleCodeEnum.NewBarcodeRule.ToString());
+
+            return newBarcode;
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="stockDetail"></param>
+        /// <param name="newBarcode"></param>
+        /// <param name="splitQuantity"></param>
+        /// <param name="afterQuantity"></param>
+        /// <param name="remark"></param>
+        /// <returns></returns>
+        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>();
+
+            Dt_MaterielInfo? materielInfo = _basicService.MaterielInfoService.Repository.QueryFirst(x => x.MaterielCode == stockDetail.MaterielCode);
+
+            Dt_MaterialCodeInfo outMaterialCodeInfo = new Dt_MaterialCodeInfo()
+            {
+                AfterQuantity = splitQuantity,
+                BatchNo = stockDetail.BatchNo,
+                FactoryArea = stockDetail.FactoryArea,
+                MaterialName = materielInfo?.MaterielName ?? stockDetail.MaterielName,
+                MaterialSpec = materielInfo?.MaterielSpec ?? "",
+                MaterialCode = stockDetail.MaterielCode,
+                NewBarcode = newBarcode,
+                OldBarcode = stockDetail.Barcode,
+                OriginalQuantity = stockDetail.StockQuantity,
+                PruchaseOrderNo = stockDetail.OrderNo,
+                SuplierCode = stockDetail.SupplyCode,
+                Unit = stockDetail.Unit,
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Remark = remark,
+                WarehouseCode = stockDetail.WarehouseCode,
+                OrderNo = orderNo,
+                OrderId = orderId,
+                ReturnStatus = 0
+            };
+            materialCodeInfos.Add(outMaterialCodeInfo);
+
+            Dt_MaterialCodeInfo returnMaterialCodeInfo = new Dt_MaterialCodeInfo()
+            {
+                AfterQuantity = stockDetail.StockQuantity - splitQuantity,
+                BatchNo = stockDetail.BatchNo,
+                FactoryArea = stockDetail.FactoryArea,
+                MaterialName = materielInfo?.MaterielName ?? stockDetail.MaterielName,
+                MaterialSpec = materielInfo?.MaterielSpec ?? "",
+                MaterialCode = stockDetail.MaterielCode,
+                NewBarcode = stockDetail.Barcode,
+                OldBarcode = stockDetail.Barcode,
+                OriginalQuantity = stockDetail.StockQuantity,
+                PruchaseOrderNo = stockDetail.OrderNo,
+                SuplierCode = stockDetail.SupplyCode,
+                Unit = stockDetail.Unit,
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Remark = remark,
+                WarehouseCode = stockDetail.WarehouseCode,
+                OrderNo = orderNo,
+                OrderId = orderId,
+                ReturnStatus = 0
+            };
+
+            materialCodeInfos.Add(returnMaterialCodeInfo);
+
+            _basicService.MaterielCodeInfoService.Repository.AddData(materialCodeInfos);
+            return materialCodeInfos;
+        }
+
+        /// <summary>
+        /// 鏇存柊璇ユ墭鐩樿鐗╂枡鐨勬墍鏈夐攣瀹氳褰曠殑绱宸插嚭搴撴暟閲�
+        /// </summary>
+        /// <param name="stockId">搴撳瓨ID</param>
+        /// <param name="materielCode">鐗╂枡缂栧彿</param>
+        /// <param name="batchNo">鎵规鍙�</param>
+        /// <param name="actualOutboundQuantity">鏈瀹為檯鍑哄簱鏁伴噺</param>
+        /// <returns></returns>
+        private void UpdateLockInfoAllocatedQuantity(int stockId, string materielCode, string batchNo, decimal actualOutboundQuantity)
+        {
+            // 鏌ヨ璇ユ墭鐩樿鐗╂枡鐨勬墍鏈夐攣瀹氳褰�
+            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
+                x.StockId == stockId &&
+                x.MaterielCode == materielCode &&
+                x.BatchNo == batchNo);
+
+            if (lockRecords != null && lockRecords.Any())
+            {
+                // 鏇存柊鎵�鏈夌浉鍏宠褰曠殑AllocatedQuantity
+                foreach (var record in lockRecords)
+                {
+                    record.AllocatedQuantity += actualOutboundQuantity;
+                }
+
+                // 鎵归噺鏇存柊
+                _outboundLockInfoRepository.UpdateData(lockRecords);
+            }
+        }
+
+        /// <summary>
+        /// 妫�鏌ュ嚭搴撳崟鏄惁瀹屾垚
+        /// </summary>
+        public bool CheckOutboundOrderCompleted(string orderNo)
+        {
+            Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
+            if (outboundOrder == null) return false;
+
+            List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id);
+
+            // 妫�鏌ユ墍鏈夋槑缁嗙殑宸插嚭鏁伴噺鏄惁閮界瓑浜庡崟鎹暟閲�
+            return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty);
+        }
+
+        /// <summary>
+        /// 妫�鏌ュ嚭搴撳崟鏄庣粏鏄惁瀹屾垚
+        /// </summary>
+        public bool CheckOutboundOrderDetailCompletedByMatCode(string orderNo, string materialCode, Dt_OutboundOrderDetail outboundOrderDetail)
+        {
+            Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
+            if (outboundOrder == null) return false;
+
+            List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && x.MaterielCode == materialCode && (string.IsNullOrEmpty(outboundOrderDetail.SupplyCode) || x.SupplyCode == outboundOrderDetail.SupplyCode) && (string.IsNullOrEmpty(outboundOrderDetail.WarehouseCode) || x.WarehouseCode == outboundOrderDetail.WarehouseCode));
+
+            // 妫�鏌ユ墍鏈夋槑缁嗙殑宸插嚭鏁伴噺鏄惁閮界瓑浜庡崟鎹暟閲�
+            return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty);
+        }
+
+        #endregion
+
+        #region 鍙栫┖绠�
+        public async Task<WebResponseContent> EmptyBox(string palletCode)
+        {
+            WebResponseContent content = new WebResponseContent();
+            try
+            {
+                var stock = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Includes(x => x.Details).Where(x => x.PalletCode == palletCode).FirstAsync();
+
+                if (stock == null)
+                {
+                    return content.Error($"鏈壘鍒版墭鐩榹palletCode}搴撳瓨淇℃伅");
+                }
+                if (stock.Details.Count > 0)
+                {
+                    return content.Error($"鎵樼洏{palletCode}杩樺瓨鍦ㄥ簱瀛樹俊鎭笉鍏佽鍙栬蛋");
+                }
+                Dt_StockInfo_Hty stockInfo_Hty = stock.Adapt<Dt_StockInfo_Hty>();
+                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);
+                _unitOfWorkManage.CommitTran();
+                return content.OK();
+            }
+            catch (Exception ex)
+            {
+                _unitOfWorkManage.RollbackTran();
+                return content.Error(ex.Message);
+            }
+        }
+
+        #endregion
+
+        #region 鍥炲簱
+        public async Task<WebResponseContent> ReturnToWarehouse(string palletCode, string OrderNo, string station)
+        {
+            WebResponseContent content = new WebResponseContent();
+            try
+            {
+                var stock = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Includes(x => x.Details).Where(x => x.PalletCode == palletCode).FirstAsync();
+
+                if (stock == null)
+                {
+                    return content.Error($"鏈壘鍒版墭鐩榹palletCode}搴撳瓨淇℃伅涓嶅厑璁稿洖搴�");
+                }
+
+                if (stock.Details.Count <= 0)
+                {
+                    stock.PalletType = (int)PalletTypeEnum.Empty;
+                    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}搴撳瓨鏈嫞閫夊畬涓嶅厑璁稿洖搴�");
+                    }
+                    stock.StockStatus = (int)StockStatusEmun.缁勭洏鏆傚瓨;
+                    stock.LocationCode = "";
+                }
+
+                var task = await _taskRepository.Db.Queryable<Dt_Task>()
+                    .Where(x => x.PalletCode == palletCode)
+                    .FirstAsync();
+
+                if (task != null)
+                {
+                    return content.Error($"鎵樼洏{palletCode}瀛樺湪浠诲姟鍥炲簱澶辫触!");
+                }
+
+                // 鍒嗛厤鏂拌揣浣�
+                var newLocation = _locationInfoService.AssignLocation(stock.LocationType);
+
+                var newTask = new Dt_Task()
+                {
+                    CurrentAddress = stations.GetValueOrDefault(station) ?? "",
+                    Grade = 0,
+                    PalletCode = palletCode,
+                    NextAddress = "",
+                    OrderNo = OrderNo,
+                    Roadway = newLocation.RoadwayNo,
+                    SourceAddress = stations.GetValueOrDefault(station) ?? "",
+                    TargetAddress = newLocation.LocationCode,
+                    TaskStatus = (int)TaskStatusEnum.New,
+                    TaskType = stock.Details.Count > 0 ? (int)TaskTypeEnum.InPick : (int)TaskTypeEnum.InEmpty,
+                    PalletType = stock.PalletType,
+                    WarehouseId = stock.WarehouseId
+                };
+                _stockInfoRepository.UpdateData(stock);
+                _taskRepository.AddData(newTask);
+
+                var moveResult = await _eSSApiService.MoveContainerAsync(new MoveContainerRequest
+                {
+                    slotCode = movestations[station],
+                    containerCode = palletCode
+                });
+                return content.OK();
+
+            }
+            catch (Exception ex)
+            {
+                return content.Error(ex.Message);
+            }
+        }
+
+        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($"鏈壘鍒拌寰呴噸鎷g殑鍗曟嵁{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
     }
 }

--
Gitblit v1.9.3