123 lines
4.0 KiB
C#
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";
|
|
}
|
|
}
|