diff --git a/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Application.Contracts/Balances/Inputs/RecommendBalanceRequestInput.cs b/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Application.Contracts/Balances/Inputs/RecommendBalanceRequestInput.cs index 29c437f45..f6c401c70 100644 --- a/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Application.Contracts/Balances/Inputs/RecommendBalanceRequestInput.cs +++ b/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Application.Contracts/Balances/Inputs/RecommendBalanceRequestInput.cs @@ -28,6 +28,11 @@ public class RecommendBalanceRequestInput [Display(Name = "库区域")] public List LocationAreas { get; set; } /// + /// 库位 + /// + [Display(Name = "库位")] + public List Locations { get; set; } + /// /// 库存状态 /// [Display(Name = "库存状态")] diff --git a/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Application/Balances/BalanceAppService.cs b/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Application/Balances/BalanceAppService.cs index 9878c2452..1759a6a5d 100644 --- a/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Application/Balances/BalanceAppService.cs +++ b/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Application/Balances/BalanceAppService.cs @@ -465,23 +465,22 @@ public class BalanceAppService } /// - /// 根据发料任务需求,算出推荐的库存 (在获取时 已经添加预占用) + /// 根据发料任务需求的库位,算出推荐的库存 (在获取时 已经添加预占用) /// /// /// /// - [HttpGet("recommend-list-location")] - public virtual async Task> GetRecommendBalancesAsync1(RecommendBalanceRequestInput input) + [HttpGet("recommend-list-location-by-locations")] + public virtual async Task> GetRecommendBalancesByLocationsAsync(RecommendBalanceRequestInput input) { var traceId = GuidGenerator.Create(); var itemCode = input.ItemCode; var qty = input.Qty; - var locationTypes = input.LocationTypes; - var locationAreas = input.LocationAreas; + var locations = input.Locations; var statuses = input.Statuses; Logger.LogDebug(traceId + "|Input:" + input); - var balances = await _balanceManager.GetRecommendBalancesAsync(traceId, itemCode, qty, locationTypes, locationAreas, statuses).ConfigureAwait(false); + var balances = await _balanceManager.GetRecommendBalancesByLocationAsync(traceId, itemCode, qty, locations, statuses).ConfigureAwait(false); var dtos = ObjectMapper.Map, List>(balances); diff --git a/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Domain/Balances/BalanceManager.cs b/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Domain/Balances/BalanceManager.cs index 526f26173..98cbe245c 100644 --- a/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Domain/Balances/BalanceManager.cs +++ b/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Domain/Balances/BalanceManager.cs @@ -436,6 +436,159 @@ public class BalanceManager : DomainService, IBalanceManager #region GetRecommendList + + /// + /// 根据库位获取推荐库位 + /// + /// + /// + /// + /// + /// + /// + /// + /// + public virtual async Task> GetRecommendBalancesByLocationAsync(Guid traceId, string itemCode, + decimal requestQty, List validLocations, + List validStatuses) + { + var recommendBalances = new List();//返回可使用的推荐库存余额 + + var item = await _itemBasicAclService.GetByCodeAsync(itemCode).ConfigureAwait(false); + if (item == null) //物品是否存在 + { + // throw new UserFriendlyException($"未找到代码为 {itemCode} 的物料记录"); + return recommendBalances; + } + + //构造查询条件 + Expression> expression = p => p.ItemCode == itemCode; + expression = expression.And(p => validStatuses.Contains(p.Status)); + expression = expression.And(p => p.IsActive); + //如果物品的有效期不是无限的, 要过滤掉过期库存 + if (item.ValidityUnit != EnumValidityUnit.Infinite) + { + expression = expression.And(p => p.ExpireDate > DateTime.Now); + } + //筛选有效库区 2023-6-29 李智慧 王旭 袁静雯 确认不需要做区域校验 + //if (validLocationAreas.Any()) + //{ + // if (!string.IsNullOrEmpty(validLocationAreas[0])) + // { + // expression = expression.And(p => validLocationAreas.Contains(p.LocationArea)); + // } + //} + var allBalances = await + (await _balanceRepository.GetDbSetAsync().ConfigureAwait(false)) + .Where(expression) + .AsNoTracking() + .ToListAsync().ConfigureAwait(false); + + var balanceLocationCodes = allBalances.Select(p => p.LocationCode).Distinct().ToList(); + var locations = await _locationAclService.GetByCodesAsync(balanceLocationCodes).ConfigureAwait(false); + //筛选有效库位类型 + if (validLocations.Any()) + { + locations = locations.Where(p => validLocations.Contains(p.Code)).ToList(); + } + var locationCodes = locations.Where(p => p.EnablePick).Select(p => p.Code).ToList(); + Logger.LogDebug(traceId + "|Locations:" + locationCodes.JoinAsString(",")); + if (!locationCodes.Any()) + { + return recommendBalances; + } + //获取物品存储关系 + var itemStoreRelationDict = await GetItemStoreRelationDictAsync(itemCode, locationCodes).ConfigureAwait(false); + //过滤掉无用的库位代码 + locationCodes = itemStoreRelationDict.Keys.ToList(); + locations = locations.Where(p => locationCodes.Contains(p.Code)).ToList(); + Logger.LogDebug(traceId + "|LocationsInStoreRelation:" + locationCodes.JoinAsString(",")); + + var usableBalances = allBalances.Where(p => locationCodes.Contains(p.LocationCode)).ToList(); + + LogDebug(traceId, usableBalances, "Balances"); + if (!usableBalances.Any()) + { + return recommendBalances; + } + + //读取该itemCode的预占用库存 + var expectOuts = await + (await _expectOutRepository.GetDbSetAsync().ConfigureAwait(false)) + .AsNoTracking() + .Where(p => p.ItemCode == itemCode + && locationCodes.Contains(p.LocationCode) + && validStatuses.Contains(p.Status)) + .ToListAsync().ConfigureAwait(false); + LogDebug(traceId, expectOuts, "ExpectOut"); + + var containerCodes = usableBalances + .Where(p => !string.IsNullOrEmpty(p.ContainerCode)) + .Select(p => p.ContainerCode) + .ToList(); + + var expectOutContainerCodes = await + (await _expectOutRepository.GetDbSetAsync().ConfigureAwait(false)) + .Where(p => containerCodes.Contains(p.ContainerCode)) + .GroupBy(p => p.ContainerCode) + .Select(d => d.Key) + .ToListAsync().ConfigureAwait(false); + + usableBalances + //扣减已占用库存 + .DecreaseExpectOutQty(expectOuts, locations) + //去除不可拆箱 拆托的且有预占用的库存余额 + .IgnoreExpectOutOfSameContainer(expectOutContainerCodes, itemStoreRelationDict, locations) + //过滤掉不允许拣料的库位 + .FilterLocationEnablePickAsync(locations) + //排序库存余额 最终可用的余额集合 + .SortByFifo(); + + LogDebug(traceId, usableBalances, "AvailableBalances"); + + var usableBalanceQueue = new Queue(usableBalances); + + var remainQty = requestQty; + while (usableBalanceQueue.TryDequeue(out var usableBalance)) + { + if (usableBalance.Qty <= 0) + { + continue; + } + + var location = locations.First(p => p.Code == usableBalance.LocationCode); + var itemStoreRelation = itemStoreRelationDict[usableBalance.LocationCode]; + + var recommendBalance = new Balance(usableBalance); + var recommendQty = + GetRecommendQty(traceId, requestQty, remainQty, usableBalance, location, itemStoreRelation); + + recommendBalance.Qty = recommendQty; + recommendBalances.Add(recommendBalance); + remainQty -= recommendQty; + //如果剩余数量小于等于零, 说明所有请求数量已满足, 退出循环 + if (remainQty <= 0) + { + break; + } + } + + return recommendBalances; + + #region Old + + // var availableQty = GetAvailableQty(traceId, requestQty, 0, usableBalances, itemStoreRelationDict, locations, recommendBalances); + // + // if (availableQty >= requestQty) + // { + // return recommendBalances; + // } + // var availableBalanceQty = usableBalances.Sum(p => p.Qty); + // throw new UserFriendlyException($"物料 {itemCode} 的可用库存余额 {availableBalanceQty} 不足"); + + #endregion + } + /// /// 获取推荐库位 /// diff --git a/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Domain/Balances/IBalanceManager.cs b/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Domain/Balances/IBalanceManager.cs index 18f0c537e..17d16cb80 100644 --- a/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Domain/Balances/IBalanceManager.cs +++ b/be/Modules/Inventory/src/Win_in.Sfs.Wms.Inventory.Domain/Balances/IBalanceManager.cs @@ -33,4 +33,5 @@ public interface IBalanceManager : IDomainService Task> GetItemCodesOfLocationAsync(string locationCode); Task ModifyAsync(Balance balance); Task> GetListByLocationTypeAndInventoryStatusAsync(string itemCode, EnumInventoryStatus enumInventoryStatus, List enumLocationTypes); + Task> GetRecommendBalancesByLocationAsync(Guid traceId, string itemCode, decimal requestQty, List validLocations, List validStatuses); }