| | |
| | | using WIDESEA_Common.LocationEnum; |
| | | using Microsoft.Extensions.Logging; |
| | | using SqlSugar; |
| | | using WIDESEA_Common.LocationEnum; |
| | | using WIDESEA_Common.OrderEnum; |
| | | using WIDESEA_Common.StockEnum; |
| | | using WIDESEA_Core; |
| | | using WIDESEA_Core.BaseRepository; |
| | | using WIDESEA_Core.BaseServices; |
| | | using WIDESEA_Core.Helper; |
| | | using WIDESEA_DTO.Stock; |
| | | using WIDESEA_IBasicService; |
| | | using WIDESEA_IOutboundService; |
| | | using WIDESEA_IRecordService; |
| | |
| | | private readonly IUnitOfWorkManage _unitOfWorkManage; |
| | | public IRepository<Dt_OutboundOrderDetail> Repository => BaseDal; |
| | | |
| | | |
| | | |
| | | private readonly IStockService _stockService; |
| | | |
| | | private readonly IOutStockLockInfoService _outStockLockInfoService; |
| | | private readonly ILocationInfoService _locationInfoService; |
| | | private readonly IBasicService _basicService; |
| | | 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; |
| | |
| | | _locationInfoService = locationInfoService; |
| | | _locationStatusChangeRecordService = locationStatusChangeRecordService; |
| | | _outboundOrderService = outboundOrderService; |
| | | _logger = logger; |
| | | |
| | | } |
| | | |
| | | |
| | | /// <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()) |
| | | { |
| | |
| | | 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); |
| | | |
| | | if (!CanReassignOrder(outboundOrder)) |
| | | { |
| | | throw new Exception($"订å{outboundOrder.OrderNo}ç¶æä¸å
è®¸éæ°åé
åºå"); |
| | | } |
| | | |
| | | List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>(); |
| | | List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>(); |
| | |
| | | |
| | | // æç©æåæ¹æ¬¡åç»å¤ç |
| | | var groupDetails = outboundOrderDetails |
| | | .GroupBy(x => new { x.MaterielCode, x.BatchNo }) |
| | | .GroupBy(x => new { x.MaterielCode, x.BatchNo, x.SupplyCode }) |
| | | .Select(x => new |
| | | { |
| | | 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) |
| | | TotalNeedQuantity = CalculateReassignNeedQuantity(x.ToList()) |
| | | }) |
| | | .Where(x => x.TotalNeedQuantity > 0) |
| | | .ToList(); |
| | |
| | | var needQuantity = item.TotalNeedQuantity; |
| | | |
| | | // è·åå¯ç¨åºåï¼æå
è¿å
åºæåºï¼ |
| | | List<Dt_StockInfo> stockInfos = _stockService.StockInfoService.GetUseableStocks(item.MaterielCode, item.BatchNo); |
| | | List<Dt_StockInfo> stockInfos = _stockService.StockInfoService.GetUseableStocks(item.MaterielCode, item.BatchNo, item.SupplyCode); |
| | | |
| | | if (!stockInfos.Any()) |
| | | { |
| | | throw new Exception($"ç©æ[{item.MaterielCode}]æ¹æ¬¡[{item.BatchNo}]æªæ¾å°å¯åé
åºå"); |
| | | throw new Exception($"ç©æ[{item.MaterielCode}]æ¹æ¬¡[{item.BatchNo}]ä¾åºå[{item.SupplyCode}]æªæ¾å°å¯åé
åºå"); |
| | | } |
| | | |
| | | // åé
åºåï¼æå
è¿å
åºï¼ |
| | | List<Dt_StockInfo> autoAssignStocks = _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) |
| | | { |
| | | throw new Exception($"ç©æ[{item.MaterielCode}]åºåä¸è¶³ï¼éè¦{needQuantity}ï¼å¯ç¨{needQuantity - residueQuantity}"); |
| | | throw new Exception($"ç©æ[{item.MaterielCode}]æ¹æ¬¡[{item.BatchNo}]åºåä¸è¶³ï¼éè¦{needQuantity}ï¼ä½æ æ³åé
ä»»ä½åºå"); |
| | | } |
| | | |
| | | outStocks.AddRange(autoAssignStocks); |
| | | |
| | | // æå
è¿å
åºåååé
é宿°éå°å个æç» |
| | | DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, 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, |
| | | List<Dt_OutStockLockInfo> outStockLockInfos, |
| | | Dt_OutboundOrder outboundOrder) |
| | | private bool CanReassignOrder(Dt_OutboundOrder outboundOrder) |
| | | { |
| | | // æå
è¿å
åºæåºåºåºåæç»ï¼å设å
å建çæç»éè¦ä¼å
æ»¡è¶³ï¼ |
| | | // å
è®¸éæ°åé
çç¶æ |
| | | var allowedStatus = new[] {OutOrderStatusEnum.æªå¼å§, OutOrderStatusEnum.åºåºä¸,OutOrderStatusEnum.é¨å宿}; |
| | | |
| | | return allowedStatus.Contains((OutOrderStatusEnum)outboundOrder.OrderStatus); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// éæ°è®¡ç®éæ±æ°éï¼èèéæ°åé
çåºæ¯ï¼ |
| | | /// </summary> |
| | | private decimal CalculateReassignNeedQuantity(List<Dt_OutboundOrderDetail> details) |
| | | { |
| | | decimal totalNeed = 0; |
| | | |
| | | foreach (var detail in details) |
| | | { |
| | | // å
³é®ä¿®æ¹ï¼éæ°åé
æ¶ï¼åªèèæªåºåºçé¨åï¼å¿½ç¥é宿°é |
| | | // å 为é宿°éå¨ååºæ¶å·²ç»è¢«éç½® |
| | | decimal remainingNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.MoveQty; |
| | | |
| | | if (remainingNeed > 0) |
| | | { |
| | | totalNeed += remainingNeed; |
| | | } |
| | | } |
| | | |
| | | return totalNeed; |
| | | } |
| | | 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) |
| | | { |
| | | // æå
è¿å
åºæåºåºåºåæç»ï¼Idå°çä¼å
ï¼ |
| | | var sortedDetails = details |
| | | .OrderBy(x => x.Id) // æIDæåºï¼å设å
å建çIDå° |
| | | .Where(d => d.OrderQuantity - d.OverOutQuantity - d.LockQuantity - d.MoveQty > 0) |
| | | .OrderBy(x => x.Id) |
| | | .ToList(); |
| | | |
| | | // æå
è¿å
åºæåºåºåï¼çäº§æ¥æææ©çä¼å
ï¼ |
| | | var sortedStockDetails = assignStocks |
| | | if (!sortedDetails.Any()) |
| | | return (true, "æ éåé
"); |
| | | |
| | | // è·åææåé
äºåºåçæç»ï¼æå
è¿å
åºæåº |
| | | var allocatedStockDetails = assignStocks |
| | | .SelectMany(x => x.Details) |
| | | .Where(x => details.Any(d => d.MaterielCode == x.MaterielCode)) |
| | | .OrderBy(x => x.ProductionDate) |
| | | .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 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 allocatedQuantityForStock)) |
| | | continue; |
| | | |
| | | if (allocatedQuantity <= 0) continue; |
| | | if (allocatedQuantityForStock <= 0) continue; |
| | | |
| | | var stockInfo = assignStocks.First(x => x.Id == stockDetail.StockId); |
| | | decimal remainingAllocate = allocatedQuantityForStock; |
| | | |
| | | // æé¡ºåºåé
ç»å个åºåºåæç» |
| | | 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 - detail.MoveQty; |
| | | if (detailNeed <= 0) continue; |
| | | |
| | | // åé
æ°é |
| | | var assignQuantity = Math.Min(remainingAllocate, detailNeed); |
| | | |
| | | // éªè¯æ¡ç æ¯å¦åå¨ |
| | | if (string.IsNullOrEmpty(stockDetail.Barcode)) |
| | | { |
| | | return (false, $"åºåæç»ID[{stockDetail.Id}]çæ¡ç 为空"); |
| | | } |
| | | |
| | | // å建åºåºéå®ä¿¡æ¯ |
| | | var lockInfo = _outStockLockInfoService.GetOutStockLockInfo( |
| | |
| | | // æ´æ°æç»çé宿°é |
| | | detail.LockQuantity += assignQuantity; |
| | | remainingAllocate -= assignQuantity; |
| | | allocatedQuantity += assignQuantity; |
| | | } |
| | | |
| | | // å¦æè¿æå©ä½åé
æ°éï¼è¯´æé»è¾æè¯¯ |
| | | // å¦æè¿æå©ä½åé
æ°éï¼è®°å½è¦å |
| | | if (remainingAllocate > 0) |
| | | { |
| | | throw new Exception($"åºååé
é»è¾é误ï¼å©ä½æªåé
æ°éï¼{remainingAllocate}"); |
| | | _logger.LogWarning($"åºååé
å仿å©ä½æ°éæªåé
: {remainingAllocate}, æ¡ç : {stockDetail.Barcode}"); |
| | | } |
| | | } |
| | | |
| | | // éªè¯æ¯å¦è³å°åé
äºä¸é¨å |
| | | if (allocatedQuantity <= 0) |
| | | { |
| | | return (false, "åºååé
å¤±è´¥ï¼æ æ³åé
任使°é"); |
| | | } |
| | | |
| | | // è®°å½åé
ç»æ |
| | | if (allocatedQuantity < totalNeedQuantity) |
| | | { |
| | | _logger.LogWarning($"åºåé¨ååé
ï¼éè¦{totalNeedQuantity}ï¼å®é
åé
{allocatedQuantity}"); |
| | | } |
| | | else |
| | | { |
| | | _logger.LogInformation($"åºåå®å
¨åé
ï¼åé
æ°é{allocatedQuantity}"); |
| | | } |
| | | |
| | | return (true, "åé
æå"); |
| | | } |
| | | |
| | | |
| | | /// <summary> |
| | | /// å建åºåºéå®ä¿¡æ¯ |
| | | /// </summary> |
| | | private void CreateOutStockLockInfos(Dt_OutboundOrder outboundOrder,List<Dt_OutboundOrderDetail> details, |
| | | List<Dt_StockInfo> assignStocks,List<Dt_OutStockLockInfo> outStockLockInfos) |
| | | |
| | | /// <summary> |
| | | /// ä¸ºåæ¹åé
åºå |
| | | /// </summary> |
| | | public async Task<(List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>)> |
| | | AssignStockForBatch(Dt_OutboundOrderDetail orderDetail, decimal batchQuantity, string batchNo) |
| | | { |
| | | foreach (var stock in assignStocks) |
| | | if (orderDetail == null) |
| | | { |
| | | // è·å该åºåä¸ç¸å
³ç©æçå¯ç¨æ¡ç ä¿¡æ¯ |
| | | var stockDetails = stock.Details |
| | | .Where(x => details.Any(d => d.MaterielCode == x.MaterielCode) && |
| | | x.StockQuantity > x.OutboundQuantity) |
| | | .OrderBy(x => x.ProductionDate) // å
è¿å
åº |
| | | .ToList(); |
| | | if (!stockDetails.Any()) continue; |
| | | throw new Exception("æªæ¾å°åºåºåæç»ä¿¡æ¯"); |
| | | } |
| | | |
| | | var stockAssignQuantity = stockDetails.Sum(x => x.OutboundQuantity); |
| | | var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>() |
| | | .FirstAsync(x => x.Id == orderDetail.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 existingAssign = outStockLockInfos |
| | | .Where(x => x.StockId == stock.Id && |
| | | details.Any(d => d.Id == x.OrderDetailId)) |
| | | .Sum(x => x.AssignQuantity); |
| | | |
| | | var availableAssign = stockAssignQuantity - existingAssign; |
| | | |
| | | if (availableAssign <= 0) continue; |
| | | |
| | | // æå
è¿å
åºåååé
æ¡ç |
| | | var barcodeAllocation = AllocateBarcodes(stockDetails, availableAssign); |
| | | |
| | | |
| | | // åé
ç»å个æç» |
| | | foreach (var detail in details.Where(d => d.LockQuantity > 0)) |
| | | // æç©æåæ¹æ¬¡åç»å¤çï¼è¿éåªæä¸ä¸ªæç»ï¼ |
| | | var groupDetails = new List<Dt_OutboundOrderDetail> { orderDetail } |
| | | .GroupBy(x => new { x.MaterielCode, x.BatchNo, x.SupplyCode }) |
| | | .Select(x => new |
| | | { |
| | | var alreadyAssigned = outStockLockInfos |
| | | .Where(x => x.OrderDetailId == detail.Id && x.StockId == stock.Id) |
| | | .Sum(x => x.AssignQuantity); |
| | | MaterielCode = x.Key.MaterielCode, |
| | | BatchNo = x.Key.BatchNo, |
| | | SupplyCode = x.Key.SupplyCode, |
| | | Details = x.ToList(), |
| | | TotalNeedQuantity = batchQuantity // 使ç¨åæ¹æ°é |
| | | }) |
| | | .Where(x => x.TotalNeedQuantity > 0) |
| | | .ToList(); |
| | | |
| | | var canAssign = Math.Min(detail.LockQuantity - alreadyAssigned, availableAssign); |
| | | foreach (var item in groupDetails) |
| | | { |
| | | var needQuantity = item.TotalNeedQuantity; |
| | | |
| | | if (canAssign > 0) |
| | | // è·åå¯ç¨åºåï¼æå
è¿å
åºæåºï¼ |
| | | 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); |
| | | |
| | | // æ£æ¥åé
ç»æ |
| | | decimal allocatedQuantity = needQuantity - residueQuantity; |
| | | if (allocatedQuantity <= 0) |
| | | { |
| | | throw new Exception($"ç©æ[{item.MaterielCode}]æ¹æ¬¡[{item.BatchNo}]åºåä¸è¶³ï¼éè¦{needQuantity}ï¼ä½æ æ³åé
ä»»ä½åºå"); |
| | | } |
| | | |
| | | outStocks.AddRange(autoAssignStocks); |
| | | |
| | | // æå
è¿å
åºåååé
é宿°éå°å个æç» |
| | | var distributionResult = DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, stockAllocations, outStockLockInfos, outboundOrder, batchNo); |
| | | |
| | | if (!distributionResult.success) |
| | | { |
| | | throw new Exception(distributionResult.message); |
| | | } |
| | | |
| | | // æ´æ°åºåºåæç»ç¶æ |
| | | UpdateOrderDetailStatus(item.Details, allocatedQuantity, needQuantity); |
| | | } |
| | | |
| | | if (outStocks.Any()) |
| | | { |
| | | locationInfos.AddRange(_locationInfoService.GetLocationInfos( |
| | | outStocks.Select(x => x.LocationCode).Distinct().ToList())); |
| | | |
| | | |
| | | } |
| | | |
| | | return (outStocks, groupDetails.SelectMany(x => x.Details).ToList(), outStockLockInfos, locationInfos); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æå
è¿å
åºåååé
é宿°é |
| | | /// </summary> |
| | | 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, |
| | | string batchNo) |
| | | { |
| | | var sortedDetails = details |
| | | .Where(d => d.OrderQuantity - d.OverOutQuantity - d.AllocatedQuantity > 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.AllocatedQuantity); |
| | | decimal allocatedQuantity = 0; |
| | | |
| | | // 为æ¯ä¸ªåºåæç»å建åé
è®°å½ |
| | | foreach (var stockDetail in allocatedStockDetails) |
| | | { |
| | | if (!stockAllocations.TryGetValue(stockDetail.Id, out decimal allocatedQuantityForStock)) |
| | | continue; |
| | | |
| | | if (allocatedQuantityForStock <= 0) continue; |
| | | |
| | | var stockInfo = assignStocks.First(x => x.Id == stockDetail.StockId); |
| | | decimal remainingAllocate = allocatedQuantityForStock; |
| | | |
| | | // æé¡ºåºåé
ç»å个åºåºåæç» |
| | | foreach (var detail in sortedDetails) |
| | | { |
| | | if (remainingAllocate <= 0) break; |
| | | |
| | | // 计ç®è¿ä¸ªæç»è¿éè¦åé
çæ°é |
| | | var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.AllocatedQuantity; |
| | | if (detailNeed <= 0) continue; |
| | | |
| | | // åé
æ°é |
| | | var assignQuantity = Math.Min(remainingAllocate, detailNeed); |
| | | |
| | | // éªè¯æ¡ç æ¯å¦åå¨ |
| | | if (string.IsNullOrEmpty(stockDetail.Barcode)) |
| | | { |
| | | // 为è¿ä¸ªåé
ç¡®å®æ¡ç |
| | | var (barcode, barcodeQuantity) = GetBarcodeForAllocation(barcodeAllocation, canAssign); |
| | | |
| | | var lockInfo = _outStockLockInfoService.GetOutStockLockInfo( |
| | | outboundOrder, detail, stock, canAssign, barcode,null); |
| | | outStockLockInfos.Add(lockInfo); |
| | | |
| | | availableAssign -= canAssign; |
| | | |
| | | // æ´æ°æ¡ç åé
è®°å½ |
| | | UpdateBarcodeAllocation(barcodeAllocation, barcode, barcodeQuantity); |
| | | return (false, $"åºåæç»ID[{stockDetail.Id}]çæ¡ç 为空"); |
| | | } |
| | | |
| | | if (availableAssign <= 0) break; |
| | | |
| | | // å建åºåºéå®ä¿¡æ¯ |
| | | var lockInfo = _outStockLockInfoService.GetOutStockLockInfo( |
| | | outboundOrder, detail, stockInfo, assignQuantity, stockDetail.Barcode, batchNo); |
| | | outStockLockInfos.Add(lockInfo); |
| | | |
| | | // æ´æ°æç»çå·²åé
æ°é |
| | | detail.AllocatedQuantity += assignQuantity; |
| | | remainingAllocate -= assignQuantity; |
| | | allocatedQuantity += assignQuantity; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æå
è¿å
åºåååé
æ¡ç |
| | | /// </summary> |
| | | private Dictionary<string, decimal> AllocateBarcodes(List<Dt_StockInfoDetail> stockDetails, decimal totalQuantity) |
| | | { |
| | | var allocation = new Dictionary<string, decimal>(); |
| | | decimal remainingQuantity = totalQuantity; |
| | | |
| | | foreach (var detail in stockDetails.OrderBy(x => x.ProductionDate)) |
| | | { |
| | | if (remainingQuantity <= 0) break; |
| | | |
| | | decimal available = detail.StockQuantity - detail.OutboundQuantity; |
| | | decimal allocate = Math.Min(available, remainingQuantity); |
| | | |
| | | allocation[detail.Barcode] = allocate; |
| | | remainingQuantity -= allocate; |
| | | } |
| | | |
| | | return allocation; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 为åé
è·ååéçæ¡ç |
| | | /// </summary> |
| | | private (string barcode, decimal quantity) GetBarcodeForAllocation(Dictionary<string, decimal> barcodeAllocation, decimal requiredQuantity) |
| | | { |
| | | foreach (var (barcode, quantity) in barcodeAllocation) |
| | | { |
| | | if (quantity >= requiredQuantity) |
| | | // å¦æè¿æå©ä½åé
æ°éï¼è®°å½è¦å |
| | | if (remainingAllocate > 0) |
| | | { |
| | | return (barcode, requiredQuantity); |
| | | _logger.LogWarning($"åºååé
å仿å©ä½æ°éæªåé
: {remainingAllocate}, æ¡ç : {stockDetail.Barcode}"); |
| | | } |
| | | } |
| | | |
| | | // 妿å个æ¡ç æ°éä¸è¶³ï¼ä½¿ç¨ç¬¬ä¸ä¸ªæ¡ç |
| | | var first = barcodeAllocation.First(); |
| | | return (first.Key, Math.Min(first.Value, requiredQuantity)); |
| | | // éªè¯æ¯å¦è³å°åé
äºä¸é¨å |
| | | if (allocatedQuantity <= 0) |
| | | { |
| | | return (false, "åºååé
å¤±è´¥ï¼æ æ³åé
任使°é"); |
| | | } |
| | | |
| | | // è®°å½åé
ç»æ |
| | | if (allocatedQuantity < totalNeedQuantity) |
| | | { |
| | | _logger.LogWarning($"åºåé¨ååé
ï¼éè¦{totalNeedQuantity}ï¼å®é
åé
{allocatedQuantity}"); |
| | | } |
| | | else |
| | | { |
| | | _logger.LogInformation($"åºåå®å
¨åé
ï¼åé
æ°é{allocatedQuantity}"); |
| | | } |
| | | |
| | | return (true, "åé
æå"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ´æ°æ¡ç åé
è®°å½ |
| | | /// </summary> |
| | | private void UpdateBarcodeAllocation(Dictionary<string, decimal> barcodeAllocation, string barcode, decimal usedQuantity) |
| | | |
| | | private void UpdateOrderDetailStatus(List<Dt_OutboundOrderDetail> details, |
| | | decimal allocatedQuantity, decimal needQuantity) |
| | | { |
| | | if (barcodeAllocation.ContainsKey(barcode)) |
| | | foreach (var detail in details) |
| | | { |
| | | barcodeAllocation[barcode] -= usedQuantity; |
| | | if (barcodeAllocation[barcode] <= 0) |
| | | var detailNeed = detail.OrderQuantity - detail.LockQuantity - detail.OverOutQuantity - detail.MoveQty; |
| | | |
| | | if (detailNeed <= 0) |
| | | { |
| | | barcodeAllocation.Remove(barcode); |
| | | // 该æç»å·²å®å
¨åé
|
| | | detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt(); |
| | | } |
| | | else if (allocatedQuantity < needQuantity) |
| | | { |
| | | // æ´ä½é¨ååé
ï¼è¯¥æç»å¯è½è¿æéæ± |
| | | detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOverPartial.ObjToInt(); |
| | | } |
| | | else |
| | | { |
| | | // æ´ä½å®å
¨åé
|
| | | detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt(); |
| | | } |
| | | } |
| | | } |
| | |
| | | /// <param name="locationStatus"></param> |
| | | /// <param name="tasks"></param> |
| | | /// <returns></returns> |
| | | public WebResponseContent LockOutboundStockDataUpdate(List<Dt_StockInfo> stockInfos, List<Dt_OutboundOrderDetail> outboundOrderDetails, |
| | | List<Dt_OutStockLockInfo> outStockLockInfos, List<Dt_LocationInfo> locationInfos, |
| | | public WebResponseContent LockOutboundStockDataUpdate(List<Dt_StockInfo> stockInfos, List<Dt_OutboundOrderDetail> outboundOrderDetails, |
| | | List<Dt_OutStockLockInfo> outStockLockInfos, List<Dt_LocationInfo> locationInfos, |
| | | LocationStatusEnum locationStatus = LocationStatusEnum.Lock, List<Dt_Task>? tasks = null) |
| | | { |
| | | try |
| | |
| | | // æ´æ°åºåç¶æ |
| | | stockInfos.ForEach(x => x.StockStatus = (int)StockStatusEmun.åºåºéå®); |
| | | _stockService.StockInfoService.Repository.UpdateData(stockInfos); |
| | | |
| | | |
| | | // æ´æ°åºåæç» |
| | | var stockDetails = stockInfos.SelectMany(x => x.Details).ToList(); |
| | | _stockService.StockInfoDetailService.Repository.UpdateData(stockDetails); |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | public override PageGridData<Dt_OutboundOrderDetail> GetPageData(PageDataOptions options) |
| | | { |
| | | //var pageGridData = base.GetPageData(options); |
| | | |
| | | ISugarQueryable<Dt_OutboundOrderDetail> sugarQueryable1 = BaseDal.Db.Queryable<Dt_OutboundOrderDetail>(); |
| | | if (!string.IsNullOrEmpty(options.Wheres)) |
| | | { |
| | | |
| | | List<SearchParameters> searchParametersList = options.Wheres.DeserializeObject<List<SearchParameters>>(); |
| | | int totalCount = 0; |
| | | if (searchParametersList.Count > 0) |
| | | { |
| | | { |
| | | SearchParameters? searchParameters = searchParametersList.FirstOrDefault(x => x.Name == nameof(Dt_InboundOrderDetail.OrderId).FirstLetterToLower()); |
| | | if (searchParameters != null) |
| | | { |
| | | sugarQueryable1 = sugarQueryable1.Where(x => x.OrderId == searchParameters.Value.ObjToInt()); |
| | | var dataList = sugarQueryable1.ToPageList(options.Page, options.Rows, ref totalCount); |
| | | return new PageGridData<Dt_OutboundOrderDetail>(totalCount, dataList); |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
| | | } |
| | | return new PageGridData<Dt_OutboundOrderDetail>(); |
| | | } |
| | | |
| | | |
| | | public (List<Dt_StockInfo>, Dt_OutboundOrderDetail, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) |
| | | AssignStockOutbound(Dt_OutboundOrderDetail outboundOrderDetail, List<StockSelectViewDTO> stockSelectViews) |
| | | { |
| | | // éªè¯ç¨æ·éæ© |
| | | (bool, string) checkResult = CheckSelectStockDeital(outboundOrderDetail, stockSelectViews); |
| | | if (!checkResult.Item1) throw new Exception(checkResult.Item2); |
| | | |
| | | Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.Id == outboundOrderDetail.OrderId); |
| | | var originalNeedQuantity = outboundOrderDetail.OrderQuantity - outboundOrderDetail.LockQuantity - outboundOrderDetail.MoveQty; |
| | | |
| | | List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>(); |
| | | List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>(); |
| | | |
| | | decimal remainingNeedQuantity = originalNeedQuantity; |
| | | decimal totalAssignedFromUserSelection = 0; |
| | | |
| | | // æå
è¿å
åºæåºç¨æ·éæ©çåºå |
| | | var userSelectedStocks = _stockService.StockInfoService.GetStockInfosByPalletCodes( |
| | | stockSelectViews.Select(x => x.PalletCode).ToList()); |
| | | |
| | | var sortedUserSelectedStocks = userSelectedStocks |
| | | .OrderBy(x => x.CreateDate) |
| | | .ToList(); |
| | | |
| | | // åé
ç¨æ·éæ©çåºå |
| | | foreach (var stock in sortedUserSelectedStocks) |
| | | { |
| | | if (remainingNeedQuantity <= 0) break; |
| | | |
| | | // è·åç¨æ·å¯¹è¯¥æççéæ©æ°é |
| | | var userSelection = stockSelectViews.FirstOrDefault(x => x.PalletCode == stock.PalletCode); |
| | | if (userSelection == null) continue; |
| | | |
| | | // 计ç®è¯¥æçå®é
å¯ç¨æ°é |
| | | var availableQuantity = CalculateAvailableQuantity(stock, outboundOrderDetail.MaterielCode, |
| | | outboundOrderDetail.BatchNo, outboundOrderDetail.SupplyCode); |
| | | |
| | | // ç¡®å®åé
æ°éï¼åç¨æ·éæ©æ°éãå¯ç¨æ°éåå©ä½éæ±çæå°å¼ |
| | | var assignQuantity = Math.Min( |
| | | Math.Min(userSelection.UseableQuantity, availableQuantity), |
| | | remainingNeedQuantity); |
| | | |
| | | if (assignQuantity <= 0) continue; |
| | | |
| | | // æ§è¡åé
|
| | | var actualAssigned = AssignStockQuantity(stock, outboundOrderDetail, assignQuantity); |
| | | if (actualAssigned > 0) |
| | | { |
| | | outStocks.Add(stock); |
| | | totalAssignedFromUserSelection += actualAssigned; |
| | | remainingNeedQuantity -= actualAssigned; |
| | | |
| | | // å建éå®è®°å½ |
| | | var lockInfo = CreateOutStockLockInfo(outboundOrder, outboundOrderDetail, stock, actualAssigned); |
| | | outStockLockInfos.Add(lockInfo); |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥ç¨æ·éæ©æ¯å¦è³å°åé
äºä¸é¨å |
| | | if (totalAssignedFromUserSelection <= 0) |
| | | { |
| | | throw new Exception("ç¨æ·éæ©çåºåæ æ³åé
任使°é"); |
| | | } |
| | | |
| | | // å¦æç¨æ·éæ©çåºåä¸å¤ï¼èªå¨åé
å©ä½é¨å |
| | | decimal autoAssignedQuantity = 0; |
| | | if (remainingNeedQuantity > 0) |
| | | { |
| | | List<Dt_StockInfo> autoStocks = _stockService.StockInfoService.GetUseableStocks( |
| | | outboundOrderDetail.MaterielCode, outboundOrderDetail.BatchNo, ""); |
| | | |
| | | // æé¤ç¨æ·å·²éæ©çæç |
| | | autoStocks = autoStocks |
| | | .Where(x => !stockSelectViews.Select(v => v.PalletCode).Contains(x.PalletCode)) |
| | | .ToList(); |
| | | |
| | | var (autoAssignStocks, stockAllocations) = _stockService.StockInfoService.GetOutboundStocks( |
| | | autoStocks, outboundOrderDetail.MaterielCode, remainingNeedQuantity, out decimal residueQuantity); |
| | | |
| | | // æ£æ¥èªå¨åé
ç»æ |
| | | autoAssignedQuantity = remainingNeedQuantity - residueQuantity; |
| | | if (autoAssignedQuantity <= 0 && remainingNeedQuantity > 0) |
| | | { |
| | | // é¨ååé
æ¯å¯ä»¥æ¥åçï¼è®°å½è¦åä½ä¸æ¥é |
| | | _logger.LogWarning($"èªå¨åé
失败ï¼å©ä½éæ±{remainingNeedQuantity}æ æ³æ»¡è¶³"); |
| | | } |
| | | else if (autoAssignedQuantity > 0) |
| | | { |
| | | outStocks.AddRange(autoAssignStocks); |
| | | |
| | | // 为èªå¨åé
çåºåå建éå®è®°å½ |
| | | var autoLockInfos = CreateLockInfosForAutoAssign(outboundOrder, outboundOrderDetail, autoAssignStocks, stockAllocations); |
| | | outStockLockInfos.AddRange(autoLockInfos); |
| | | } |
| | | } |
| | | |
| | | // æ´æ°é宿°é |
| | | outboundOrderDetail.LockQuantity += totalAssignedFromUserSelection + autoAssignedQuantity; |
| | | |
| | | // æ´æ°ç¶æ |
| | | UpdateOrderDetailStatus(outboundOrderDetail, remainingNeedQuantity - autoAssignedQuantity); |
| | | |
| | | List<Dt_LocationInfo> locationInfos = _locationInfoService.GetLocationInfos(outStocks.Select(x => x.LocationCode).ToList()); |
| | | |
| | | return (outStocks, outboundOrderDetail, outStockLockInfos, locationInfos); |
| | | } |
| | | |
| | | // è¾
婿¹æ³ |
| | | private decimal CalculateAvailableQuantity(Dt_StockInfo stock, string materielCode, string batchNo, string supplyCode) |
| | | { |
| | | var relevantDetails = stock.Details |
| | | .Where(d => d.MaterielCode == materielCode && |
| | | (string.IsNullOrEmpty(batchNo) || d.BatchNo == batchNo) && |
| | | (string.IsNullOrEmpty(supplyCode) || d.SupplyCode == supplyCode)) |
| | | .ToList(); |
| | | |
| | | return relevantDetails.Sum(d => d.StockQuantity - d.OutboundQuantity); |
| | | } |
| | | |
| | | private decimal AssignStockQuantity(Dt_StockInfo stock, Dt_OutboundOrderDetail detail, decimal assignQuantity) |
| | | { |
| | | decimal remainingAssign = assignQuantity; |
| | | |
| | | // æå
è¿å
åºåé
åºåæç» |
| | | var sortedDetails = stock.Details |
| | | .Where(d => d.MaterielCode == detail.MaterielCode && |
| | | d.BatchNo == detail.BatchNo && |
| | | (d.StockQuantity - d.OutboundQuantity) > 0) |
| | | .OrderBy(d => d.CreateDate) |
| | | .ToList(); |
| | | |
| | | foreach (var stockDetail in sortedDetails) |
| | | { |
| | | if (remainingAssign <= 0) break; |
| | | |
| | | var available = stockDetail.StockQuantity - stockDetail.OutboundQuantity; |
| | | var assign = Math.Min(available, remainingAssign); |
| | | |
| | | stockDetail.OutboundQuantity += assign; |
| | | remainingAssign -= assign; |
| | | } |
| | | |
| | | return assignQuantity - remainingAssign; // è¿åå®é
åé
æ°é |
| | | } |
| | | |
| | | private Dt_OutStockLockInfo CreateOutStockLockInfo(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail detail, |
| | | Dt_StockInfo stock, decimal quantity) |
| | | { |
| | | var barcode = stock.Details |
| | | .Where(d => !string.IsNullOrEmpty(d.Barcode)) |
| | | .Select(d => d.Barcode) |
| | | .FirstOrDefault(); |
| | | |
| | | return _outStockLockInfoService.GetOutStockLockInfo(outboundOrder, detail, stock, quantity, barcode); |
| | | } |
| | | |
| | | private List<Dt_OutStockLockInfo> CreateLockInfosForAutoAssign(Dt_OutboundOrder outboundOrder, |
| | | Dt_OutboundOrderDetail detail, List<Dt_StockInfo> stocks, Dictionary<int, decimal> allocations) |
| | | { |
| | | var lockInfos = new List<Dt_OutStockLockInfo>(); |
| | | |
| | | foreach (var stock in stocks.OrderBy(x => x.CreateDate)) |
| | | { |
| | | if (allocations.TryGetValue(stock.Id, out decimal quantity) && quantity > 0) |
| | | { |
| | | var lockInfo = CreateOutStockLockInfo(outboundOrder, detail, stock, quantity); |
| | | lockInfos.Add(lockInfo); |
| | | } |
| | | } |
| | | |
| | | return lockInfos; |
| | | } |
| | | |
| | | private void UpdateOrderDetailStatus(Dt_OutboundOrderDetail detail, decimal remainingQuantity) |
| | | { |
| | | if (remainingQuantity <= 0) |
| | | { |
| | | detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt(); |
| | | } |
| | | else |
| | | { |
| | | detail.OrderDetailStatus = OrderDetailStatusEnum.AssignOverPartial.ObjToInt(); |
| | | } |
| | | } |
| | | |
| | | private (bool, string) CheckSelectStockDeital(Dt_OutboundOrderDetail outboundOrderDetail, List<StockSelectViewDTO> stockSelectViews) |
| | | { |
| | | var needQuantity = outboundOrderDetail.OrderQuantity - outboundOrderDetail.LockQuantity - outboundOrderDetail.MoveQty; |
| | | |
| | | if (needQuantity <= 0) |
| | | { |
| | | return (false, "åºåºåæç»æ éåé
åºå"); |
| | | } |
| | | |
| | | // æ£æ¥æ»éæ©æ°éæ¯å¦å¤§äº0 |
| | | var totalSelected = stockSelectViews.Sum(x => x.UseableQuantity); |
| | | if (totalSelected <= 0) |
| | | { |
| | | return (false, "ç¨æ·éæ©çåºåæ°éå¿
须大äº0"); |
| | | } |
| | | |
| | | // æ£æ¥æ¯ä¸ªæççå¯ç¨æ°é |
| | | foreach (var selection in stockSelectViews) |
| | | { |
| | | if (selection.UseableQuantity <= 0) |
| | | { |
| | | return (false, $"æç[{selection.PalletCode}]çéæ©æ°éå¿
须大äº0"); |
| | | } |
| | | |
| | | var stock = _stockService.StockInfoService.GetStockInfoByPalletCode(selection.PalletCode); |
| | | if (stock == null) |
| | | { |
| | | return (false, $"æç[{selection.PalletCode}]ä¸åå¨"); |
| | | } |
| | | |
| | | var available = CalculateAvailableQuantity(stock, outboundOrderDetail.MaterielCode, |
| | | outboundOrderDetail.BatchNo, outboundOrderDetail.SupplyCode); |
| | | |
| | | if (available <= 0) |
| | | { |
| | | return (false, $"æç[{selection.PalletCode}]没æå¯ç¨åºå"); |
| | | } |
| | | } |
| | | |
| | | return (true, "éªè¯éè¿"); |
| | | } |
| | | } |
| | | } |