feat(clgl): 完善材料匹配与入库条码查询
通过按材料编码优化库存匹配并增加条码查询入口,减少材料主编码选择歧义,便于按项目追溯入库条码。
This commit is contained in:
@@ -149,18 +149,89 @@ namespace BLL
|
||||
}
|
||||
}
|
||||
|
||||
// 以下三个集合只服务于“按材料 Code 挑选材料主编码”的场景:
|
||||
// remainingRequiredNumByCode:当前明细及后续明细的剩余总需求;
|
||||
// stocksByCode:同一 Code 下所有可用的材料主编码库存;
|
||||
// usedMaterialCodesByCode:本轮匹配已经使用过的材料主编码,用于优先继续消耗其余量。
|
||||
Dictionary<string, decimal> remainingRequiredNumByCode = null;
|
||||
Dictionary<string, List<Tw_MaterialStockOutput>> stocksByCode = null;
|
||||
Dictionary<string, HashSet<string>> usedMaterialCodesByCode = null;
|
||||
if (matchByMaterialCodeLibCode)
|
||||
{
|
||||
// 按 Code 汇总全部需求,选择新主编码时可判断一个库存能否覆盖剩余需求。
|
||||
remainingRequiredNumByCode = requiredMaterials
|
||||
.Where(x => !string.IsNullOrEmpty(x.MaterialCode))
|
||||
.GroupBy(x => x.MaterialCode)
|
||||
.ToDictionary(x => x.Key, x => x.Sum(y => y.NeedNum ?? 0));
|
||||
|
||||
// 出库申请预扣后,仅保留仍有可用数量的主编码库存。
|
||||
stocksByCode = stockList
|
||||
.Where(x => !string.IsNullOrEmpty(x.Code) && (x.StockNum ?? 0) > 0)
|
||||
.GroupBy(x => x.Code)
|
||||
.ToDictionary(x => x.Key, x => x.ToList());
|
||||
|
||||
// 每个 Code 独立记录已经启用的主编码,避免不同材料之间相互影响。
|
||||
usedMaterialCodesByCode = stocksByCode.ToDictionary(x => x.Key, x => new HashSet<string>());
|
||||
}
|
||||
|
||||
foreach (var material in requiredMaterials)
|
||||
{
|
||||
material.Id = Guid.NewGuid().ToString();
|
||||
// 材料导入只保存材料编码 Code,匹配时按 Code 从库存中挑选实际材料主编码 PipeLineMatCode。
|
||||
var stock = matchByMaterialCodeLibCode
|
||||
? stockList
|
||||
.Where(x => x.Code == material.MaterialCode && (x.StockNum ?? 0) > 0)
|
||||
// 优先选择库存能覆盖本条需求的材料主编码,再按主编码稳定排序,保证匹配结果可复现。
|
||||
.OrderByDescending(x => (x.StockNum ?? 0) >= (material.NeedNum ?? 0))
|
||||
.ThenBy(x => x.PipeLineMatCode)
|
||||
.FirstOrDefault()
|
||||
: stockList.FirstOrDefault(x => x.PipeLineMatCode == material.MaterialCode);
|
||||
Tw_MaterialStockOutput stock = null;
|
||||
if (matchByMaterialCodeLibCode)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(material.MaterialCode) && stocksByCode.ContainsKey(material.MaterialCode))
|
||||
{
|
||||
decimal needNum = material.NeedNum ?? 0;
|
||||
decimal remainingRequiredNum = remainingRequiredNumByCode[material.MaterialCode];
|
||||
// StockNum 会在每条明细匹配后原地扣减,这里读取的是本轮实时剩余库存。
|
||||
var candidates = stocksByCode[material.MaterialCode]
|
||||
.Where(x => (x.StockNum ?? 0) > 0)
|
||||
.ToList();
|
||||
var usedMaterialCodes = usedMaterialCodesByCode[material.MaterialCode];
|
||||
|
||||
// 第一优先级:复用已使用且能完整满足当前明细的主编码,减少主编码种类;
|
||||
// 在多个已用主编码都满足时,选择库存最小的,使其更接近用完。
|
||||
stock = candidates
|
||||
.Where(x => usedMaterialCodes.Contains(x.PipeLineMatCode) && (x.StockNum ?? 0) >= needNum)
|
||||
.OrderBy(x => x.StockNum ?? 0)
|
||||
.ThenBy(x => x.PipeLineMatCode)
|
||||
.FirstOrDefault()
|
||||
// 第二优先级:启用一个能覆盖剩余总需求的新主编码,保证后续仍只使用这一种主编码;
|
||||
// 若有多个可选,选择库存最小的,减少最终余料。
|
||||
?? candidates
|
||||
.Where(x => !usedMaterialCodes.Contains(x.PipeLineMatCode) && (x.StockNum ?? 0) >= remainingRequiredNum)
|
||||
.OrderBy(x => x.StockNum ?? 0)
|
||||
.ThenBy(x => x.PipeLineMatCode)
|
||||
.FirstOrDefault()
|
||||
// 第三优先级:无法一次覆盖剩余总需求时,选择刚好能满足当前明细且余量最小的新主编码。
|
||||
?? candidates
|
||||
.Where(x => !usedMaterialCodes.Contains(x.PipeLineMatCode) && (x.StockNum ?? 0) >= needNum)
|
||||
.OrderBy(x => x.StockNum ?? 0)
|
||||
.ThenBy(x => x.PipeLineMatCode)
|
||||
.FirstOrDefault()
|
||||
// 所有库存都不足以完整满足当前明细时,选择库存最大的主编码以保证匹配量最大;
|
||||
// 库存相同时优先已使用的主编码,最后按主编码排序保证结果稳定。
|
||||
?? candidates
|
||||
.OrderByDescending(x => x.StockNum ?? 0)
|
||||
.ThenByDescending(x => usedMaterialCodes.Contains(x.PipeLineMatCode))
|
||||
.ThenBy(x => x.PipeLineMatCode)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (stock != null)
|
||||
{
|
||||
usedMaterialCodes.Add(stock.PipeLineMatCode);
|
||||
}
|
||||
// 当前明细处理完后,只保留后续明细的总需求供下一轮选码判断。
|
||||
remainingRequiredNumByCode[material.MaterialCode] -= needNum;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 旧调用传入的是材料主编码,保持原来的精确匹配逻辑。
|
||||
stock = stockList.FirstOrDefault(x => x.PipeLineMatCode == material.MaterialCode);
|
||||
}
|
||||
var thisMaterialStockNum = stock?.StockNum ?? 0;
|
||||
if (thisMaterialStockNum >= material.NeedNum)
|
||||
{
|
||||
|
||||
@@ -39,13 +39,16 @@ namespace BLL
|
||||
(string.IsNullOrEmpty(table.Id) || x.Id.Contains(table.Id)) &&
|
||||
(string.IsNullOrEmpty(table.InputDetailId) || x.InputDetailId.Contains(table.InputDetailId)) &&
|
||||
(string.IsNullOrEmpty(table.InputMasterId) || x.InputMasterId.Contains(table.InputMasterId)) &&
|
||||
(string.IsNullOrEmpty(table.MaterialCode) || x.MaterialCode.Contains(table.MaterialCode))
|
||||
(string.IsNullOrEmpty(table.ProjectId) || master.ProjectId == table.ProjectId) &&
|
||||
(string.IsNullOrEmpty(table.MaterialCode) || x.MaterialCode.Contains(table.MaterialCode)) &&
|
||||
(string.IsNullOrEmpty(table.BarCode) || x.BarCode.Contains(table.BarCode))
|
||||
orderby master.CusBillCode, x.MaterialCode, x.Id
|
||||
select new Tw_InputDetailBarCodeOutput
|
||||
{
|
||||
Id = x.Id,
|
||||
InputDetailId = x.InputDetailId,
|
||||
InputMasterId = x.InputMasterId,
|
||||
ProjectId = master.ProjectId,
|
||||
CusBillCode = master.CusBillCode,
|
||||
MaterialCode = x.MaterialCode,
|
||||
Code = mat.Code,
|
||||
|
||||
Reference in New Issue
Block a user