1
huangxiaoqiang
2025-12-17 cbf0d3caa8474101886488ec2d3165a28f9c6ab0
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -1,12 +1,17 @@
using SqlSugar;
using AutoMapper;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using SqlSugar;
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.CalcOut;
using WIDESEA_DTO.ReturnMES;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_IRecordService;
@@ -17,7 +22,7 @@
{
    public partial class OutboundService : IOutboundService
    {
        private readonly IMapper _mapper;
        public IUnitOfWorkManage _unitOfWorkManage { get; }
        public IOutboundOrderDetailService OutboundOrderDetailService { get; }
@@ -27,32 +32,40 @@
        public IOutStockLockInfoService OutboundStockLockInfoService { get; }
        private readonly ISqlSugarClient Db;
        private readonly IOutboundOrderDetailService _detailService;
        private readonly IOutboundOrderService _outboundOrderService;
        private readonly IOutStockLockInfoService _outboundLockInfoService;
        private readonly IStockInfoService _stockInfoService;
        private readonly IStockInfoDetailService _stockDetailService;
        private readonly ILocationInfoService _locationInfoService;
        private readonly IStockQuantityChangeRecordService _stockChangeService;
        private readonly IStockInfoDetail_HtyService _stockDetailHistoryService;
        private readonly IBasicService _basicService;
        public OutboundService(IUnitOfWorkManage unitOfWorkManage, IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService, IStockInfoService stockInfoService, IStockInfoDetailService stockDetailService, ILocationInfoService locationInfoService, IStockQuantityChangeRecordService stockQuantityChangeRecordService, IStockInfoDetail_HtyService stockDetailHistoryService)
        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;
        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)
        {
            _mapper = mapper;
            _unitOfWorkManage = unitOfWorkManage;
            Db = _unitOfWorkManage.GetDbClient();
            OutboundOrderDetailService = outboundOrderDetailService;
            OutboundOrderService = outboundOrderService;
            OutboundStockLockInfoService = outboundStockLockInfoService;
            _detailService = outboundOrderDetailService;
            _outboundOrderService = outboundOrderService;
            _outboundLockInfoService = outboundStockLockInfoService;
            _stockInfoService = stockInfoService;
            _stockDetailService = stockDetailService;
            _locationInfoService = locationInfoService;
            _stockChangeService = stockQuantityChangeRecordService;
            _stockDetailHistoryService = stockDetailHistoryService;
            _detailRepository = detailRepository;
            _outboundRepository = outboundRepository;
            _outboundLockInfoRepository = outboundLockInfoRepository;
            _stockInfoRepository = stockInfoRepository;
            _stockDetailRepository = stockDetailRepository;
            _locationInfoRepository = basicService.LocationInfoService.Repository;
            _stockChangeRepository = stockChangeRepository;
            _stockDetailHistoryRepository = stockDetailHistoryRepository;
            _basicService = basicService;
            _feedbackMesService = feedbackMesService;
        }
        #region å‡ºåº“分配
        /// <summary>
        /// åˆ†æ‹£å‡ºåº“操作
        /// </summary>
@@ -97,15 +110,31 @@
                        if (outStockLockInfo != null)
                        {
                            outStockLockInfo = item;
                            Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                            if (task != null)
                            {
                                outStockLockInfo.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);
                    }
                    tasks.AddRange(materielPickedDetails.Tasks);
                    foreach (var item in materielPickedDetails.Tasks)
                    {
                        Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                        if (task == null)
                        {
                            tasks.Add(item);
                        }
                    }
                    pickedDetails.AddRange(materielPickedDetails.PickedDetails);
@@ -113,9 +142,17 @@
                    foreach (var detail in materielCalc.Details)
                    {
                        decimal lockQuantity = (detail.OrderQuantity - detail.OverOutQuantity);
                        detail.LockQuantity += lockQuantity; // å¢žåŠ é”å®šæ•°é‡ ä¸æ›´æ–° OverOutQuantity å’Œ OrderDetailStatus,因为还没有实际出库
                        outboundOrderDetails.Add(detail);
                        if (lockQuantity < materielCalc.UnallocatedQuantity)
                        {
                            detail.LockQuantity += lockQuantity; // å¢žåŠ é”å®šæ•°é‡ ä¸æ›´æ–° OverOutQuantity å’Œ OrderDetailStatus,因为还没有实际出库
                            outboundOrderDetails.Add(detail);
                        }
                        else
                        {
                            detail.LockQuantity += materielCalc.UnallocatedQuantity;
                            outboundOrderDetails.Add(detail);
                            break;
                        }
                    }
                }
@@ -123,7 +160,7 @@
                UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.出库中);
                // 4. æ›´æ–°å‡ºåº“单明细锁定数量
                _detailService.Repository.UpdateData(outboundOrderDetails);
                _detailRepository.UpdateData(outboundOrderDetails);
                // 5. æ›´æ–°åº“存状态
                UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.出库锁定.ObjToInt());
@@ -163,7 +200,7 @@
            try
            {
                Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.OrderNo == request.OrderNo);
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
                if (outboundOrder == null)
                {
                    result.CanOutbound = false;
@@ -172,10 +209,15 @@
                }
                result.FactoryArea = outboundOrder.FactoryArea;
                result.IsMultiDetail = request.IsMultiDetail;
                // èŽ·å–é€‰æ‹©çš„å‡ºåº“æ˜Žç»†
                List<Dt_OutboundOrderDetail> selectedDetails = _detailService.Repository.QueryData(x => x.OrderId == outboundOrder.Id && request.DetailIds.Contains(x.Id));
                List<Dt_OutboundOrderDetail> selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && request.DetailIds.Contains(x.Id));
                if (outboundOrder.IsBatch == 1)
                {
                    selectedDetails = _detailRepository.QueryData(x => x.WarehouseCode == selectedDetails.First().WarehouseCode && x.MaterielCode == selectedDetails.First().MaterielCode && x.BatchNo == selectedDetails.First().BatchNo && x.SupplyCode == selectedDetails.First().SupplyCode);
                }
                if (!selectedDetails.Any())
                {
@@ -183,7 +225,6 @@
                    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();
@@ -196,7 +237,7 @@
                result.OutboundOrder = outboundOrder;
                result.SelectedDetails = selectedDetails;
                if (request.IsMultiDetail)
                if (outboundOrder.IsBatch == 0)
                {
                    // å¤šæ˜Žç»†å‡ºåº“:按物料分组处理
                    result.MaterielCalculations = CalcMaterielOutboundQuantities(outboundOrder, selectedDetails.ToList());
@@ -211,15 +252,21 @@
                        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 (singleDetail.OrderQuantity - singleDetail.LockQuantity - singleDetail.MoveQty <= 0)
                    if (orderQuantity - lockQuantity - moveQuantity < request.OutboundQuantity.Value || orderQuantity - overQuantity - moveQuantity < request.OutboundQuantity.Value)
                    {
                        result.CanOutbound = false;
                        result.ErrorMessage = $"本次出库数量 {request.OutboundQuantity.Value} è¶…过可出库数量 {singleDetail.OrderQuantity - singleDetail.LockQuantity - singleDetail.MoveQty}";
                        result.ErrorMessage = $"本次出库数量 {request.OutboundQuantity.Value} è¶…过可出库数量 {orderQuantity - lockQuantity - moveQuantity}";
                        return result;
                    }
                    result.MaterielCalculations = new List<MaterielOutboundCalculationDTO>()
                    {
                        new MaterielOutboundCalculationDTO
@@ -229,12 +276,12 @@
                            BatchNo = singleDetail.BatchNo,
                            SupplyCode = singleDetail.SupplyCode,
                            WarehouseCode = singleDetail.WarehouseCode,
                            TotalOrderQuantity = singleDetail.OrderQuantity - singleDetail.MoveQty,
                            TotalOverOutQuantity = singleDetail.OverOutQuantity,
                            AssignedQuantity = singleDetail.LockQuantity,
                            UnallocatedQuantity = singleDetail.OrderQuantity - singleDetail.LockQuantity - singleDetail.MoveQty,
                            MovedQuantity = singleDetail.MoveQty,
                            Details = new List<Dt_OutboundOrderDetail>() { singleDetail }
                            TotalOrderQuantity = orderQuantity - moveQuantity,
                            TotalOverOutQuantity = overQuantity,
                            AssignedQuantity = lockQuantity,
                            UnallocatedQuantity = request.OutboundQuantity.Value,
                            MovedQuantity = moveQuantity,
                            Details = selectedDetails
                        }
                    };
                }
@@ -281,7 +328,7 @@
                    UnallocatedQuantity = g.Sum(x => x.OrderQuantity - x.LockQuantity - x.MoveQty),
                    MovedQuantity = g.Sum(x => x.MoveQty),
                    Details = g.ToList(),
                    OutStockLockInfos = _outboundLockInfoService.Repository.QueryData(x => x.MaterielCode == g.Key.MaterielCode && x.BatchNo == g.Key.BatchNo && x.OrderType == (int)outboundOrder.OrderType && x.OrderNo == outboundOrder.OrderNo)
                    OutStockLockInfos = _outboundLockInfoRepository.QueryData(x => x.MaterielCode == g.Key.MaterielCode && x.BatchNo == g.Key.BatchNo && x.OrderType == (int)outboundOrder.OrderType && x.OrderNo == outboundOrder.OrderNo)
                })
                .ToList();
@@ -353,9 +400,11 @@
                decimal availableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0);
                if (availableQuantity <= 0) continue;
                // è®¡ç®—该托盘可分配数量
                decimal allocateQuantity = Math.Min(remainingQuantity, availableQuantity);
                (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) actualAllocated = AllocateStockQuantity(stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail, request, lockStockMap.GetValueOrDefault(stock.Id, new List<Dt_OutStockLockInfo>()), stockDetailMap);
                // æœ¬æ¬¡åˆ†é…çš„æ•°é‡
                decimal actualAllocatedQuantity = actualAllocated.ActualAllocatedQuantity;
                if (actualAllocatedQuantity > 0)
@@ -436,7 +485,7 @@
        private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea)
        {
            // åŸºç¡€æŸ¥è¯¢æ¡ä»¶ï¼šç‰©æ–™ç¼–号、批次号(如果提供)、库存数量>0
            ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailService.Repository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0);
            ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0);
            // æ ¹æ®æ¡ä»¶æ·»åŠ ä¾›åº”å•†ç¼–å·åŒ¹é…ï¼ˆä¸ä¸ºç©ºæ—¶æ‰éœ€è¦åŒ¹é…ï¼‰
            if (!string.IsNullOrEmpty(materielCalc.SupplyCode))
@@ -465,11 +514,11 @@
            List<Dt_StockInfoDetail> stockDetailList = stockDetails.ToList();
            // èŽ·å–å¯ç”¨è´§ä½ç¼–å·
            List<string> locationCodes = _locationInfoService.Repository.QueryData(x => (x.LocationStatus == LocationStatusEnum.InStock.ObjToInt() /*|| x.LocationStatus == LocationStatusEnum.Lock.ObjToInt()*/) && x.EnableStatus == EnableStatusEnum.Normal.ObjToInt()).Select(x => x.LocationCode).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 = _stockInfoService.Repository.QueryData(x => stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.入库完成.ObjToInt() /*|| x.StockStatus == StockStatusEmun.出库锁定.ObjToInt()*/) && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode));
            List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x => stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.入库完成.ObjToInt() /*|| x.StockStatus == StockStatusEmun.出库锁定.ObjToInt()*/) && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode));
            // åœ¨å†…存中关联数据
            foreach (var stockInfo in stockInfos)
@@ -502,7 +551,7 @@
                // è®¡ç®—总库存数量
                decimal totalQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
                List<Dt_OutStockLockInfo> outStockLockInfos = allocatedData.Where(x => x.StockId == stockInfo.Id).ToList();
                List<Dt_OutStockLockInfo> outStockLockInfos = allocatedData.Where(x => x.StockId == stockInfo.Id && x.MaterielCode == materielCalc.MaterielCode).ToList();
                // è®¡ç®—已分配数量
                decimal allocatedQuantity = outStockLockInfos.Sum(x => x.AllocatedQuantity);
@@ -510,7 +559,6 @@
                lockStockMap[stockInfo.Id] = outStockLockInfos;
            }
            return (availableStockMap, lockStockMap);
        }
@@ -560,7 +608,7 @@
                    // æ›´æ–°åˆ†é…å‡ºåº“量
                    decimal beforeAssignQuantity = totalAllocatedQuantity; // æœ¬æ¬¡åˆ†é…å‰çš„æ€»ç´¯è®¡é‡
                    lockInfo.AssignQuantity += actualAllocatedQuantity; // ç´¯åŠ æœ¬æ¬¡åˆ†é…æ•°é‡
                    lockInfo.AssignQuantity += actualAllocatedQuantity; // æœ¬æ¬¡åˆ†é…æ•°é‡
                    lockInfo.AllocatedQuantity = beforeAssignQuantity; // è®°å½•本次分配前的总累计量
                    lockInfoList.Add(lockInfo);
@@ -635,7 +683,7 @@
        private decimal CalcTotalAllocatedQuantity(List<Dt_OutStockLockInfo> lockInfos, int stockId, string materielCode)
        {
            // æŸ¥è¯¢è¯¥æ‰˜ç›˜è¯¥ç‰©æ–™åœ¨æ‰€æœ‰é”å®šè®°å½•中的最大已分配数量
            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoService.Repository.QueryData(x =>
            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
                x.StockId == stockId &&
                x.MaterielCode == materielCode);
@@ -655,11 +703,11 @@
        {
            try
            {
                Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.OrderNo == orderNo);
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
                if (outboundOrder == null) return false;
                outboundOrder.OrderStatus = status;
                _outboundOrderService.Repository.UpdateData(outboundOrder);
                _outboundRepository.UpdateData(outboundOrder);
                return true;
            }
            catch
@@ -678,13 +726,13 @@
        {
            try
            {
                List<Dt_StockInfo> stockInfos = _stockInfoService.Repository.QueryData(x => palletCodes.Contains(x.PalletCode));
                List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x => palletCodes.Contains(x.PalletCode));
                stockInfos.ForEach(stockInfo =>
                {
                    stockInfo.StockStatus = status;
                });
                _stockInfoService.Repository.UpdateData(stockInfos);
                _stockInfoRepository.UpdateData(stockInfos);
                return true;
            }
            catch
@@ -703,13 +751,13 @@
        {
            try
            {
                List<Dt_LocationInfo> locationInfos = _locationInfoService.Repository.QueryData(x => locationCodes.Contains(x.LocationCode));
                List<Dt_LocationInfo> locationInfos = _locationInfoRepository.QueryData(x => locationCodes.Contains(x.LocationCode));
                locationInfos.ForEach(x =>
                {
                    x.LocationStatus = status;
                });
                _locationInfoService.Repository.UpdateData(locationInfos);
                _locationInfoRepository.UpdateData(locationInfos);
                return true;
            }
            catch
@@ -728,10 +776,10 @@
            try
            {
                List<Dt_OutStockLockInfo> updateData = outStockLockInfos.Where(x => x.Id > 0).ToList();
                _outboundLockInfoService.Repository.UpdateData(updateData);
                _outboundLockInfoRepository.UpdateData(updateData);
                List<Dt_OutStockLockInfo> addData = outStockLockInfos.Where(x => x.Id <= 0).ToList();
                _outboundLockInfoService.Repository.AddData(addData);
                _outboundLockInfoRepository.AddData(addData);
                return true;
            }
@@ -740,6 +788,7 @@
                return false;
            }
        }
        #endregion
        /// <summary>
@@ -755,7 +804,7 @@
            try
            {
                // 1. æ ¹æ®æ‰˜ç›˜å·æŸ¥æ‰¾åº“存信息
                Dt_StockInfo stockInfo = _stockInfoService.Repository.QueryFirst(x => x.PalletCode == request.PalletCode);
                Dt_StockInfo stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == request.PalletCode);
                if (stockInfo == null)
                {
                    response.Success = false;
@@ -764,7 +813,7 @@
                }
                // 2. æ ¹æ®æ¡ç æŸ¥æ‰¾åº“存明细
                Dt_StockInfoDetail stockDetail = _stockDetailService.Repository.QueryFirst(x => x.Barcode == request.Barcode);
                Dt_StockInfoDetail stockDetail = _stockDetailRepository.QueryFirst(x => x.Barcode == request.Barcode);
                if (stockDetail == null)
                {
                    response.Success = false;
@@ -781,7 +830,7 @@
                }
                // 4. æŸ¥æ‰¾å‡ºåº“单信息
                Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(o => o.OrderNo == request.OrderNo);
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
                if (outboundOrder == null)
                {
                    response.Success = false;
@@ -789,17 +838,8 @@
                    return WebResponseContent.Instance.Error($"出库单 {request.OrderNo} ä¸å­˜åœ¨");
                }
                // 5. æŸ¥æ‰¾å‡ºåº“单明细信息
                List<Dt_OutboundOrderDetail> outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockDetail);
                if (!outboundOrderDetails.Any())
                {
                    response.Success = false;
                    response.Message = $"未找到匹配的出库单明细,物料:{stockDetail.MaterielCode},批次:{stockDetail.BatchNo}";
                    return WebResponseContent.Instance.Error($"未找到匹配的出库单明细,物料:{stockDetail.MaterielCode},批次:{stockDetail.BatchNo}");
                }
                // 6. æŸ¥æ‰¾é”å®šè®°å½•
                Dt_OutStockLockInfo lockInfo = _outboundLockInfoService.Repository.QueryFirst(x =>
                // 5. æŸ¥æ‰¾é”å®šè®°å½•
                Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
                    x.OrderNo == request.OrderNo &&
                    x.StockId == stockInfo.Id &&
                    x.MaterielCode == stockDetail.MaterielCode &&
@@ -810,6 +850,25 @@
                    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. è®¡ç®—实际出库量
@@ -826,9 +885,16 @@
                    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;
                string newBarcode = string.Empty;
                List<MaterialCodeReturnDTO> returnDTOs = new List<MaterialCodeReturnDTO>();
                // 9. å¼€å¯äº‹åŠ¡
                _unitOfWorkManage.BeginTran();
@@ -839,7 +905,7 @@
                    // æ ¹æ®æ˜¯å¦æ‹†åŒ…执行不同的操作
                    if (isUnpacked)
                    {
                        newBarcode = PerformUnpackOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault());
                        returnDTOs = PerformUnpackOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault());
                    }
                    else
                    {
@@ -852,29 +918,71 @@
                    {
                        if (allocatedQuantity <= 0) break;
                        if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity)
                        //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;
                        //}
                        if (item.LockQuantity - item.OverOutQuantity >= allocatedQuantity)
                        {
                            item.OverOutQuantity += allocatedQuantity;
                            item.CurrentDeliveryQty += allocatedQuantity;
                            allocatedQuantity = 0;
                        }
                        else
                        {
                            allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity);
                            item.OverOutQuantity = item.OrderQuantity - item.MoveQty;
                            allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
                            item.OverOutQuantity = item.LockQuantity;
                            item.CurrentDeliveryQty = item.LockQuantity;
                        }
                        updateDetails.Add(item);
                        List<Barcodes> barcodesList = new List<Barcodes>();
                        Barcodes barcodes = new Barcodes
                        {
                            Barcode = request.Barcode,
                            Qty = actualOutboundQuantity,
                            SupplyCode = stockDetail?.SupplyCode ?? "",
                            BatchNo = stockDetail?.BatchNo ?? "",
                            Unit = stockDetail?.Unit ?? ""
                        };
                        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;
                    // æ›´æ–°é”å®šè®°å½•
                    _outboundLockInfoService.Repository.UpdateData(lockInfo);
                    if (lockInfo.SortedQuantity == lockInfo.AssignQuantity)
                    {
                        _outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.自动完成);
                    }
                    else
                    {
                        // æ›´æ–°é”å®šè®°å½•
                        _outboundLockInfoRepository.UpdateData(lockInfo);
                    }
                    // æ›´æ–°å‡ºåº“单明细的已出库数量
                    _detailService.Repository.UpdateData(updateDetails);
                    _detailRepository.UpdateData(updateDetails);
                    // æ›´æ–°é”å®šè®°å½•的累计已出库数量(需要更新该托盘该物料的所有相关记录)
                    UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity);
                    //UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity);
                    // æäº¤äº‹åŠ¡
                    _unitOfWorkManage.CommitTran();
@@ -891,19 +999,21 @@
                        BeforeQuantity = beforeQuantity,
                        AfterQuantity = isUnpacked ? actualOutboundQuantity : 0,
                        ChangeQuantity = -actualOutboundQuantity,
                        IsUnpacked = isUnpacked
                        IsUnpacked = isUnpacked,
                        MaterialCodes = returnDTOs
                    };
                    response.Success = true;
                    response.Message = isUnpacked ? $"拆包出库完成,已生成新条码:{newBarcode}" : "出库完成";
                    response.Message = "出库完成";
                    response.ScannedDetail = scannedDetail;
                    response.UpdatedDetails = updateDetails;
                    response.NewBarcode = newBarcode;
                    // æ£€æŸ¥å‡ºåº“单是否完成
                    if (CheckOutboundOrderCompleted(request.OrderNo))
                    {
                        UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
                        //todo: å›žä¼ MES
                    }
                }
                catch (Exception ex)
@@ -929,11 +1039,11 @@
        /// <param name="orderId"></param>
        /// <param name="stockDetail"></param>
        /// <returns></returns>
        private List<Dt_OutboundOrderDetail> FindMatchingOutboundDetails(int orderId, Dt_StockInfoDetail stockDetail)
        private List<Dt_OutboundOrderDetail> FindMatchingOutboundDetails(int orderId, Dt_StockInfoDetail stockDetail, List<int> detailIds)
        {
            List<Dt_OutboundOrderDetail> details = _detailService.Repository.QueryData(x =>
            List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x =>
                x.OrderId == orderId &&
                x.MaterielCode == stockDetail.MaterielCode && x.OrderQuantity - x.MoveQty > x.OverOutQuantity);
                x.MaterielCode == stockDetail.MaterielCode && x.OrderQuantity - x.MoveQty > x.OverOutQuantity && detailIds.Contains(x.Id));
            // ç²¾ç¡®åŒ¹é…ï¼šå¤„理null值的批次、供应商、仓库
            List<Dt_OutboundOrderDetail> exactMatches = details.Where(x =>
@@ -960,7 +1070,6 @@
                stockDetail.StockQuantity);
        }
        /// <summary>
        /// æ‰§è¡Œæ‹†åŒ…操作
        /// </summary>
@@ -971,10 +1080,16 @@
        /// <param name="beforeQuantity"></param>
        /// <param name="taskNum"></param>
        /// <returns></returns>
        private string PerformUnpackOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
        private List<MaterialCodeReturnDTO> PerformUnpackOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
            decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum)
        {
            string newBarcode = GenerateNewBarcode();
            string remark = $"拆包记录,原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库条码:{newBarcode}, å‡ºåº“数量:{actualOutboundQuantity},回库条码:{request.Barcode},回库数量:{stockDetail.StockQuantity - actualOutboundQuantity},操作者:{request.Operator}";
            List<Dt_MaterialCodeInfo> materialCodeInfos = CreateMaterialCodeInfos(stockDetail, newBarcode, actualOutboundQuantity, remark);
            List<MaterialCodeReturnDTO> returnDTOs = _mapper.Map<List<MaterialCodeReturnDTO>>(materialCodeInfos);
            // ä¿å­˜åŽŸå§‹åº“å­˜æ˜Žç»†åˆ°åŽ†å²è®°å½•
            Dt_StockInfoDetail_Hty originalHistoryRecord = new Dt_StockInfoDetail_Hty
@@ -1000,7 +1115,7 @@
                WarehouseCode = stockDetail.WarehouseCode,
                Remark = $"拆包前原始记录,原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
            };
            _stockDetailHistoryService.Repository.AddData(originalHistoryRecord);
            _stockDetailHistoryRepository.AddData(originalHistoryRecord);
            // ä¿å­˜å‰©ä½™éƒ¨åˆ†åˆ°åŽ†å²è®°å½•
            decimal remainingQuantity = stockDetail.StockQuantity - actualOutboundQuantity;
@@ -1008,8 +1123,9 @@
            {
                // æ›´æ–°åŽŸåº“å­˜æ˜Žç»†
                stockDetail.StockQuantity = remainingQuantity;
                //stockDetail.Barcode = newBarcode;
                stockDetail.Remark = $"拆包后更新,原条码:{request.Barcode},新数量:{remainingQuantity},操作者:{request.Operator}";
                _stockDetailService.Repository.UpdateData(stockDetail);
                _stockDetailRepository.UpdateData(stockDetail);
            }
            // è®°å½•拆包变动
@@ -1032,9 +1148,9 @@
                WarehouseCode = stockDetail.WarehouseCode,
                Remark = $"拆包出库,原条码:{request.Barcode},新条码:{newBarcode},出库数量:{actualOutboundQuantity},剩余:{remainingQuantity},操作者:{request.Operator}"
            };
            _stockChangeService.Repository.AddData(unpackChangeRecord);
            _stockChangeRepository.AddData(unpackChangeRecord);
            return newBarcode;
            return returnDTOs;
        }
        /// <summary>
@@ -1067,10 +1183,10 @@
                WarehouseCode = stockDetail.WarehouseCode,
                Remark = $"出库完成删除,条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
            };
            _stockDetailHistoryService.Repository.AddData(historyRecord);
            _stockDetailHistoryRepository.AddData(historyRecord);
            // åˆ é™¤åº“存明细记录
            _stockDetailService.Repository.DeleteData(stockDetail);
            _stockDetailRepository.DeleteData(stockDetail);
            // è®°å½•库存变动
            Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord
@@ -1092,7 +1208,7 @@
                WarehouseCode = stockDetail.WarehouseCode,
                Remark = $"出库完成删除库存明细,条码:{request.Barcode},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
            };
            _stockChangeService.Repository.AddData(changeRecord);
            _stockChangeRepository.AddData(changeRecord);
        }
        /// <summary>
@@ -1104,9 +1220,67 @@
            // ä½¿ç”¨æ—¶é—´æˆ³å’Œéšæœºæ•°ç”Ÿæˆå”¯ä¸€æ¡ç 
            string newBarcode = string.Empty;
            //todo é‡æ–°ç”Ÿæˆæ¡ç é€»è¾‘
            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)
        {
            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
            };
            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
            };
            materialCodeInfos.Add(returnMaterialCodeInfo);
            _basicService.MaterielCodeInfoService.Repository.AddData(materialCodeInfos);
            return materialCodeInfos;
        }
        /// <summary>
@@ -1120,7 +1294,7 @@
        private void UpdateLockInfoAllocatedQuantity(int stockId, string materielCode, string batchNo, decimal actualOutboundQuantity)
        {
            // æŸ¥è¯¢è¯¥æ‰˜ç›˜è¯¥ç‰©æ–™çš„æ‰€æœ‰é”å®šè®°å½•
            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoService.Repository.QueryData(x =>
            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
                x.StockId == stockId &&
                x.MaterielCode == materielCode &&
                x.BatchNo == batchNo);
@@ -1134,7 +1308,7 @@
                }
                // æ‰¹é‡æ›´æ–°
                _outboundLockInfoService.Repository.UpdateData(lockRecords);
                _outboundLockInfoRepository.UpdateData(lockRecords);
            }
        }
@@ -1143,13 +1317,13 @@
        /// </summary>
        public bool CheckOutboundOrderCompleted(string orderNo)
        {
            Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.OrderNo == orderNo);
            Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
            if (outboundOrder == null) return false;
            List<Dt_OutboundOrderDetail> details = _detailService.Repository.QueryData(x => x.OrderId == outboundOrder.Id);
            List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id);
            // æ£€æŸ¥æ‰€æœ‰æ˜Žç»†çš„已出数量是否都等于单据数量
            return details.All(x => x.OverOutQuantity >= x.OrderQuantity);
            return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty);
        }
    }
}