feat(clgl): 完善材料匹配与入库条码查询

通过按材料编码优化库存匹配并增加条码查询入口,减少材料主编码选择歧义,便于按项目追溯入库条码。
This commit is contained in:
2026-09-16 15:47:51 +08:00
parent ffb9c2a95c
commit 9810280e42
14 changed files with 450 additions and 12 deletions
+79 -8
View File
@@ -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)
{
+4 -1
View File
@@ -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,
@@ -20,7 +20,7 @@
/// </summary>
public static Model.MaterialCodeLibBarCodeOutput GetBarCodeMaterialInfo(string barCode)
{
return (from detailBarCode in Funs.DB.Tw_InputDetailBarCode
var result= (from detailBarCode in Funs.DB.Tw_InputDetailBarCode
join x in Funs.DB.HJGL_MaterialCodeLib on detailBarCode.MaterialCode equals x.MaterialCode
where detailBarCode.BarCode == barCode
select new Model.MaterialCodeLibBarCodeOutput
@@ -36,6 +36,24 @@
BarCode = detailBarCode.BarCode,
Quantity = detailBarCode.Quantity
}).FirstOrDefault();
if (result==null)
{
result = (from x in Funs.DB.HJGL_MaterialCodeLib
where x.MaterialCode == barCode
select new Model.MaterialCodeLibBarCodeOutput
{
MaterialCode = x.MaterialCode,
Code = x.Code,
HeatNo = x.HeatNo,
BatchNo = x.BatchNo,
MaterialName = x.MaterialName,
MaterialDef = x.MaterialDef,
MaterialSpec = x.MaterialSpec,
MaterialUnit = x.MaterialUnit
}).FirstOrDefault();
}
return result;
}
/// <summary>