pan
2025-11-25 b736d2069a049a0465a3a8814ab72afabb6a187c
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -49,7 +49,8 @@
        /// <summary>
        /// åˆ†é…å‡ºåº“库存  æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…
        /// </summary>
        public (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) AssignStockOutbound(List<Dt_OutboundOrderDetail> outboundOrderDetails)
        public (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>)
       AssignStockOutbound(List<Dt_OutboundOrderDetail> outboundOrderDetails)
        {
            if (!outboundOrderDetails.Any())
            {
@@ -61,21 +62,22 @@
                throw new Exception("请勿同时操作多个单据明细");
            }
            var orderId = outboundOrderDetails.First().OrderId;
            var outboundOrder = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .First(x => x.Id == outboundOrderDetails.First().OrderId);
                .First(x => x.Id == orderId);
            List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>();
            List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>();
            List<Dt_LocationInfo> locationInfos = new List<Dt_LocationInfo>();
            // æŒ‰ç‰©æ–™ã€æ‰¹æ¬¡ã€ä¾›åº”商分组
            // æŒ‰ç‰©æ–™å’Œæ‰¹æ¬¡åˆ†ç»„处理
            var groupDetails = outboundOrderDetails
                .GroupBy(x => new { x.MaterielCode, x.BatchNo, x.SupplyCode })
                .Select(x => new
                {
                    x.Key.MaterielCode,
                    x.Key.BatchNo,
                    x.Key.SupplyCode,
                    MaterielCode = x.Key.MaterielCode,
                    BatchNo = x.Key.BatchNo,
                    SupplyCode = x.Key.SupplyCode,
                    Details = x.ToList(),
                    TotalNeedQuantity = x.Sum(v => v.OrderQuantity - v.OverOutQuantity - v.LockQuantity - v.MoveQty)
                })
@@ -86,101 +88,169 @@
            {
                var needQuantity = item.TotalNeedQuantity;
                // èŽ·å–å¯ç”¨åº“å­˜ï¼ˆå·²æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’åºï¼‰
                List<Dt_StockInfo> stockInfos = _stockService.StockInfoService.GetUseableStocks(
                    item.MaterielCode, item.BatchNo, item.SupplyCode);
                // èŽ·å–å¯ç”¨åº“å­˜ï¼ˆæŒ‰å…ˆè¿›å…ˆå‡ºæŽ’åºï¼‰
                List<Dt_StockInfo> stockInfos = _stockService.StockInfoService.GetUseableStocks(item.MaterielCode, item.BatchNo, item.SupplyCode);
                if (!stockInfos.Any())
                {
                    throw new Exception($"物料[{item.MaterielCode}]批次[{item.BatchNo}]未找到可分配库存");
                }
                // åˆ†é…åº“å­˜
                var (autoAssignStocks, stockAllocations) = _stockService.StockInfoService.GetOutboundStocks(
                    stockInfos, item.MaterielCode, needQuantity, out decimal residueQuantity);
                // åˆ†é…åº“存(按先进先出)
                var (autoAssignStocks, stockAllocations) = _stockService.StockInfoService.GetOutboundStocks(stockInfos, item.MaterielCode, needQuantity, out decimal residueQuantity);
                // æ£€æŸ¥åˆ†é…ç»“æžœ
                if (residueQuantity > 0)
                // æ£€æŸ¥åˆ†é…ç»“果:如果完全无法分配(分配数量为0),则报错
                decimal allocatedQuantity = needQuantity - residueQuantity;
                if (allocatedQuantity <= 0)
                {
                    var allocatedQuantity = needQuantity - residueQuantity;
                    throw new Exception($"物料[{item.MaterielCode}]库存不足,需要{needQuantity},实际分配{allocatedQuantity}");
                    throw new Exception($"物料[{item.MaterielCode}]批次[{item.BatchNo}]库存不足,需要{needQuantity},但无法分配任何库存");
                }
                outStocks.AddRange(autoAssignStocks);
                // æŒ‰å…ˆè¿›å…ˆå‡ºåˆ†é…é”å®šæ•°é‡
                DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, stockAllocations,
                    outStockLockInfos, outboundOrder);
                // æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
                var distributionResult = DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, stockAllocations, outStockLockInfos, outboundOrder);
                if (!distributionResult.success)
                {
                    throw new Exception(distributionResult.message);
                }
                // æ›´æ–°å‡ºåº“单明细状态
                UpdateOrderDetailStatus(item.Details, allocatedQuantity, needQuantity);
            }
            locationInfos.AddRange(_locationInfoService.GetLocationInfos(
                outStocks.Select(x => x.LocationCode).Distinct().ToList()));
            if (outStocks.Any())
            {
                locationInfos.AddRange(_locationInfoService.GetLocationInfos(
                    outStocks.Select(x => x.LocationCode).Distinct().ToList()));
            }
            return (outStocks, outboundOrderDetails, outStockLockInfos, locationInfos);
        }
        /// <summary>
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
        /// </summary>
        private void DistributeLockQuantityByFIFO(List<Dt_OutboundOrderDetail> details,List<Dt_StockInfo> assignStocks,Dictionary<int, decimal> stockAllocations,List<Dt_OutStockLockInfo> outStockLockInfos,
    Dt_OutboundOrder outboundOrder)
        private (bool success, string message) DistributeLockQuantityByFIFO(
           List<Dt_OutboundOrderDetail> details,
           List<Dt_StockInfo> assignStocks,
           Dictionary<int, decimal> stockAllocations,
           List<Dt_OutStockLockInfo> outStockLockInfos,
           Dt_OutboundOrder outboundOrder)
        {
            var sortedStocks = assignStocks.OrderBy(x => x.CreateDate).ToList();
            var totalNeedQuantity = details.Sum(d => d.OrderQuantity - d.OverOutQuantity - d.LockQuantity - d.MoveQty);
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序出库单明细(Id小的优先)
            var sortedDetails = details
                .Where(d => d.OrderQuantity - d.OverOutQuantity - d.LockQuantity - d.MoveQty > 0)
                .OrderBy(x => x.Id)
                .ToList();
            if (!sortedDetails.Any())
                return (true, "无需分配");
            // èŽ·å–æ‰€æœ‰åˆ†é…äº†åº“å­˜çš„æ˜Žç»†ï¼ŒæŒ‰å…ˆè¿›å…ˆå‡ºæŽ’åº
            var allocatedStockDetails = assignStocks
                .SelectMany(x => x.Details)
                .Where(x => stockAllocations.ContainsKey(x.Id) && stockAllocations[x.Id] > 0)
                .OrderBy(x => x.CreateDate)
                .ThenBy(x => x.StockId)
                .ToList();
            if (!allocatedStockDetails.Any())
            {
                return (false, "没有可分配的库存明细");
            }
            decimal totalNeedQuantity = sortedDetails.Sum(d =>
                d.OrderQuantity - d.OverOutQuantity - d.LockQuantity - d.MoveQty);
            decimal allocatedQuantity = 0;
            foreach (var stock in sortedStocks)
            // ä¸ºæ¯ä¸ªåº“存明细创建分配记录
            foreach (var stockDetail in allocatedStockDetails)
            {
                if (allocatedQuantity >= totalNeedQuantity) break;
                if (!stockAllocations.TryGetValue(stock.Id, out decimal stockAllocation) || stockAllocation <= 0)
                if (!stockAllocations.TryGetValue(stockDetail.Id, out decimal allocatedQuantityForStock))
                    continue;
                var sortedDetails = details
                    .Where(d => d.OrderQuantity - d.OverOutQuantity - d.LockQuantity - d.MoveQty > 0)
                    .OrderBy(x => x.Id)
                    .ToList();
                if (allocatedQuantityForStock <= 0) continue;
                var stockInfo = assignStocks.First(x => x.Id == stockDetail.StockId);
                decimal remainingAllocate = allocatedQuantityForStock;
                // æŒ‰é¡ºåºåˆ†é…ç»™å„个出库单明细
                foreach (var detail in sortedDetails)
                {
                    if (stockAllocation <= 0) break;
                    if (remainingAllocate <= 0) break;
                    // è®¡ç®—这个明细还需要分配的数量
                    var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity - detail.MoveQty;
                    if (detailNeed <= 0) continue;
                    var assignQuantity = Math.Min(stockAllocation, detailNeed);
                    // åˆ†é…æ•°é‡
                    var assignQuantity = Math.Min(remainingAllocate, detailNeed);
                    // ä½¿ç”¨åº“存中的有效条码
                    var barcode = stock.Details
                        .Where(d => !string.IsNullOrEmpty(d.Barcode))
                        .Select(d => d.Barcode)
                        .FirstOrDefault();
                    if (string.IsNullOrEmpty(barcode))
                    // éªŒè¯æ¡ç æ˜¯å¦å­˜åœ¨
                    if (string.IsNullOrEmpty(stockDetail.Barcode))
                    {
                        throw new Exception($"库存ID[{stock.Id}]的条码为空");
                        return (false, $"库存明细ID[{stockDetail.Id}]的条码为空");
                    }
                    // åˆ›å»ºå‡ºåº“锁定信息
                    var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
                        outboundOrder, detail, stock, assignQuantity, barcode);
                        outboundOrder, detail, stockInfo, assignQuantity, stockDetail.Barcode);
                    outStockLockInfos.Add(lockInfo);
                    // æ›´æ–°æ˜Žç»†çš„锁定数量
                    detail.LockQuantity += assignQuantity;
                    stockAllocation -= assignQuantity;
                    remainingAllocate -= assignQuantity;
                    allocatedQuantity += assignQuantity;
                }
                    if (allocatedQuantity >= totalNeedQuantity) break;
                // å¦‚果还有剩余分配数量,记录警告
                if (remainingAllocate > 0)
                {
                    _logger.LogWarning($"库存分配后仍有剩余数量未分配: {remainingAllocate}, æ¡ç : {stockDetail.Barcode}");
                }
            }
            // éªŒè¯æ˜¯å¦å®Œå…¨åˆ†é…
            // éªŒè¯æ˜¯å¦è‡³å°‘分配了一部分
            if (allocatedQuantity <= 0)
            {
                return (false, "库存分配失败,无法分配任何数量");
            }
            // è®°å½•分配结果
            if (allocatedQuantity < totalNeedQuantity)
            {
                _logger.LogWarning($"库存分配不完全,需要{totalNeedQuantity},实际分配{allocatedQuantity}");
                _logger.LogWarning($"库存部分分配,需要{totalNeedQuantity},实际分配{allocatedQuantity}");
            }
            else
            {
                _logger.LogInformation($"库存完全分配,分配数量{allocatedQuantity}");
            }
            return (true, "分配成功");
        }
        private void UpdateOrderDetailStatus(List<Dt_OutboundOrderDetail> details,
    decimal allocatedQuantity, decimal needQuantity)
        {
            foreach (var detail in details)
            {
                var detailNeed = detail.OrderQuantity - detail.LockQuantity - detail.OverOutQuantity - detail.MoveQty;
                if (detailNeed <= 0)
                {
                    // è¯¥æ˜Žç»†å·²å®Œå…¨åˆ†é…
                    detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt();
                }
                else if (allocatedQuantity < needQuantity)
                {
                    // æ•´ä½“部分分配,该明细可能还有需求
                    detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOverPartial.ObjToInt();
                }
                else
                {
                    // æ•´ä½“完全分配
                    detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt();
                }
            }
        }
        /// <summary>
        /// å‡ºåº“库存分配后,更新数据库数据
        /// </summary>