pan
2025-11-12 ca4a0e7ffc11dc6f4c19b75f625444b06768ea15
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -1,4 +1,5 @@
using WIDESEA_Common.LocationEnum;
using Microsoft.Extensions.Logging;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
@@ -24,8 +25,8 @@
        private readonly IRecordService _recordService;
        private readonly IOutboundOrderService _outboundOrderService;
        private readonly ILocationStatusChangeRecordService _locationStatusChangeRecordService;
        public OutboundOrderDetailService(IRepository<Dt_OutboundOrderDetail> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IBasicService basicService, IRecordService recordService, ILocationInfoService locationInfoService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IOutboundOrderService outboundOrderService) : base(BaseDal)
        private readonly ILogger<OutboundOrderDetailService> _logger;
        public OutboundOrderDetailService(IRepository<Dt_OutboundOrderDetail> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IBasicService basicService, IRecordService recordService, ILocationInfoService locationInfoService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IOutboundOrderService outboundOrderService, ILogger<OutboundOrderDetailService> logger) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockService = stockService;
@@ -35,6 +36,7 @@
            _locationInfoService = locationInfoService;
            _locationStatusChangeRecordService = locationStatusChangeRecordService;
            _outboundOrderService = outboundOrderService;
            _logger = logger;
        }
@@ -85,10 +87,10 @@
                }
                // åˆ†é…åº“存(按先进先出)
                List<Dt_StockInfo> autoAssignStocks = _stockService.StockInfoService.GetOutboundStocks(
                var (autoAssignStocks, stockAllocations) = _stockService.StockInfoService.GetOutboundStocks(
                    stockInfos, item.MaterielCode, needQuantity, out decimal residueQuantity);
                if (residueQuantity > 0)
                if (residueQuantity > 0 && residueQuantity == needQuantity)
                {
                    throw new Exception($"物料[{item.MaterielCode}]库存不足,需要{needQuantity},可用{needQuantity - residueQuantity}");
                }
@@ -96,7 +98,7 @@
                outStocks.AddRange(autoAssignStocks);
                // æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
                DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, outStockLockInfos, outboundOrder);
                DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, stockAllocations, outStockLockInfos, outboundOrder);
            }
            locationInfos.AddRange(_locationInfoService.GetLocationInfos(outStocks.Select(x => x.LocationCode).Distinct().ToList()));
@@ -105,52 +107,59 @@
        }
        /// <summary>
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»† - ä¿®å¤ç‰ˆæœ¬
        /// </summary>
        private void DistributeLockQuantityByFIFO(
            List<Dt_OutboundOrderDetail> details,
            List<Dt_StockInfo> assignStocks,
            Dictionary<int, decimal> stockAllocations,
            List<Dt_OutStockLockInfo> outStockLockInfos,
            Dt_OutboundOrder outboundOrder)
        {
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序出库单明细(假设先创建的明细需要优先满足)
            var sortedDetails = details
                .OrderBy(x => x.Id) // æŒ‰ID排序,假设先创建的ID小
                .Where(d => d.OrderQuantity - d.OverOutQuantity - d.LockQuantity > 0) // åªå¤„理还需要分配的数量
                .OrderBy(x => x.Id)
                .ToList();
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序库存(生产日期最早的优先)
            var sortedStockDetails = assignStocks
            if (!sortedDetails.Any()) return;
            // èŽ·å–æ‰€æœ‰åˆ†é…äº†åº“å­˜çš„æ˜Žç»†ï¼ŒæŒ‰å…ˆè¿›å…ˆå‡ºæŽ’åº
            var allocatedStockDetails = assignStocks
                .SelectMany(x => x.Details)
                .Where(x => details.Any(d => d.MaterielCode == x.MaterielCode))
                .Where(x => stockAllocations.ContainsKey(x.Id))
                .OrderBy(x => x.ProductionDate)
                .ThenBy(x => x.StockId)
                .ToList();
            // ä¸ºæ¯ä¸ªåº“存明细创建分配记录
            foreach (var stockDetail in sortedStockDetails)
            foreach (var stockDetail in allocatedStockDetails)
            {
                var stockInfo = assignStocks.First(x => x.Id == stockDetail.StockId);
                var allocatedQuantity = stockDetail.OutboundQuantity; // è¿™ä¸ªåº“存明细分配的数量
                if (!stockAllocations.TryGetValue(stockDetail.Id, out decimal allocatedQuantity))
                    continue;
                if (allocatedQuantity <= 0) continue;
                // æŒ‰é¡ºåºåˆ†é…ç»™å„个出库单明细
                var stockInfo = assignStocks.First(x => x.Id == stockDetail.StockId);
                decimal remainingAllocate = allocatedQuantity;
                // æŒ‰é¡ºåºåˆ†é…ç»™å„个出库单明细
                foreach (var detail in sortedDetails)
                {
                    if (remainingAllocate <= 0) break;
                    // è®¡ç®—这个明细还需要分配的数量
                    var alreadyAssigned = outStockLockInfos
                        .Where(x => x.OrderDetailId == detail.Id && x.StockId == stockInfo.Id)
                        .Sum(x => x.AssignQuantity);
                    var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity - alreadyAssigned;
                    var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity;
                    if (detailNeed <= 0) continue;
                    // åˆ†é…æ•°é‡
                    var assignQuantity = Math.Min(remainingAllocate, detailNeed);
                    // éªŒè¯æ¡ç æ˜¯å¦å­˜åœ¨
                    if (string.IsNullOrEmpty(stockDetail.Barcode))
                    {
                        throw new Exception($"库存明细ID[{stockDetail.Id}]的条码为空");
                    }
                    // åˆ›å»ºå‡ºåº“锁定信息
                    var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
@@ -162,10 +171,32 @@
                    remainingAllocate -= assignQuantity;
                }
                // å¦‚果还有剩余分配数量,说明逻辑有误
                // å¦‚果还有剩余分配数量,重新分配或记录警告
                if (remainingAllocate > 0)
                {
                    throw new Exception($"库存分配逻辑错误,剩余未分配数量:{remainingAllocate}");
                    // é‡æ–°åˆ†é…ç»™å…¶ä»–需要分配的明细
                    foreach (var detail in sortedDetails)
                    {
                        if (remainingAllocate <= 0) break;
                        var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity;
                        if (detailNeed <= 0) continue;
                        var assignQuantity = Math.Min(remainingAllocate, detailNeed);
                        var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
                            outboundOrder, detail, stockInfo, assignQuantity, stockDetail.Barcode);
                        outStockLockInfos.Add(lockInfo);
                        detail.LockQuantity += assignQuantity;
                        remainingAllocate -= assignQuantity;
                    }
                    // å¦‚果还有剩余,记录警告但不抛出异常
                    if (remainingAllocate > 0)
                    {
                        _logger.LogWarning($"库存分配后仍有剩余数量未分配: {remainingAllocate}, æ¡ç : {stockDetail.Barcode}");
                    }
                }
            }
        }
@@ -219,7 +250,7 @@
                        var (barcode, barcodeQuantity) = GetBarcodeForAllocation(barcodeAllocation, canAssign);
                        var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
                            outboundOrder, detail, stock, canAssign, barcode,null);
                            outboundOrder, detail, stock, canAssign, barcode);
                        outStockLockInfos.Add(lockInfo);
                        availableAssign -= canAssign;