Files
BI/server/DongfangHydro.Dashboard.Api/Services/ProgressAggregationService.cs
T

123 lines
4.0 KiB
C#

using DongfangHydro.Dashboard.Api.Domain;
namespace DongfangHydro.Dashboard.Api.Services;
public sealed class ProgressAggregationService
{
public void Recalculate(ProductionOrder order, IReadOnlyCollection<ProductionNode> nodes)
{
var childrenByParent = nodes
.Where(node => node.ParentNodeId.HasValue)
.GroupBy(node => node.ParentNodeId!.Value)
.ToDictionary(group => group.Key, group => group.ToList());
var roots = nodes.Where(node => node.ParentNodeId is null).ToList();
foreach (var rootNode in roots)
{
RecalculateNode(rootNode, childrenByParent);
}
var root = roots.FirstOrDefault(node => node.NodeType == "order") ?? roots.FirstOrDefault();
if (root is null)
{
return;
}
order.RequiredQty = root.RequiredQty;
order.CompletedQty = root.CompletedQty;
order.Progress = root.Progress;
order.Status = root.Status;
order.RiskLevel = root.RiskLevel;
order.DelayDays = root.DelayDays;
}
private static void RecalculateNode(
ProductionNode node,
IReadOnlyDictionary<Guid, List<ProductionNode>> childrenByParent)
{
if (!childrenByParent.TryGetValue(node.Id, out var children) || children.Count == 0)
{
node.Progress = Percentage(node.CompletedQty, node.RequiredQty);
if (node.Progress >= 100)
{
node.Status = "done";
node.RiskLevel = "normal";
node.DelayDays = 0;
node.DelayReason = string.Empty;
}
return;
}
foreach (var child in children)
{
RecalculateNode(child, childrenByParent);
}
var childRequiredQty = children.Sum(child => child.RequiredQty);
var weightedProgress = childRequiredQty == 0
? 0
: children.Sum(child => child.Progress * child.RequiredQty) / (double)childRequiredQty;
node.RequiredQty = node.RequiredQty > 0 ? node.RequiredQty : childRequiredQty;
node.CompletedQty = (int)Math.Round(
node.RequiredQty * weightedProgress / 100d,
MidpointRounding.AwayFromZero);
node.DefectQty = children.Sum(child => child.DefectQty);
node.Progress = Math.Clamp(
(int)Math.Round(weightedProgress, MidpointRounding.AwayFromZero),
0,
100);
node.DelayDays = children.Max(child => child.DelayDays);
node.RiskLevel = AggregateRisk(children);
node.Status = AggregateStatus(children);
}
private static int Percentage(int completed, int required)
{
return required <= 0
? 0
: Math.Clamp((int)Math.Round(completed * 100d / required, MidpointRounding.AwayFromZero), 0, 100);
}
private static string AggregateRisk(IEnumerable<ProductionNode> children)
{
var childList = children.ToList();
if (childList.Any(child =>
child.RiskLevel == "critical"
|| child.Status == "blocked"
|| child.DelayDays >= 3))
{
return "critical";
}
return childList.Any(child =>
child.RiskLevel == "warning"
|| child.Status == "delayed"
|| child.DelayDays > 0)
? "warning"
: "normal";
}
private static string AggregateStatus(IReadOnlyCollection<ProductionNode> children)
{
if (children.Any(child => child.Status == "blocked"))
{
return "blocked";
}
if (children.Any(child => child.Status == "delayed" || child.DelayDays > 0))
{
return "delayed";
}
if (children.All(child => child.Status == "done"))
{
return "done";
}
return children.Any(child => child.Status == "in_progress" || child.CompletedQty > 0)
? "in_progress"
: "waiting";
}
}