feat: initialize manufacturing progress dashboard
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user