|
|
@ -436,6 +436,159 @@ public class BalanceManager : DomainService, IBalanceManager |
|
|
|
|
|
|
|
#region GetRecommendList
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 根据库位获取推荐库位
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="traceId"></param>
|
|
|
|
/// <param name="itemCode"></param>
|
|
|
|
/// <param name="requestQty"></param>
|
|
|
|
/// <param name="validLocationTypes"></param>
|
|
|
|
/// <param name="validLocationAreas"></param>
|
|
|
|
/// <param name="validStatuses"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
/// <exception cref="UserFriendlyException"></exception>
|
|
|
|
public virtual async Task<List<Balance>> GetRecommendBalancesByLocationAsync(Guid traceId, string itemCode, |
|
|
|
decimal requestQty, List<string> validLocations, |
|
|
|
List<EnumInventoryStatus> validStatuses) |
|
|
|
{ |
|
|
|
var recommendBalances = new List<Balance>();//返回可使用的推荐库存余额
|
|
|
|
|
|
|
|
var item = await _itemBasicAclService.GetByCodeAsync(itemCode).ConfigureAwait(false); |
|
|
|
if (item == null) //物品是否存在
|
|
|
|
{ |
|
|
|
// throw new UserFriendlyException($"未找到代码为 {itemCode} 的物料记录");
|
|
|
|
return recommendBalances; |
|
|
|
} |
|
|
|
|
|
|
|
//构造查询条件
|
|
|
|
Expression<Func<Balance, bool>> 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<Balance>(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
|
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 获取推荐库位
|
|
|
|
/// </summary>
|
|
|
|