Files

369 lines
15 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using DongfangHydro.Dashboard.Api.Domain;
using DongfangHydro.Dashboard.Api.Services;
using Microsoft.EntityFrameworkCore;
namespace DongfangHydro.Dashboard.Api.Data;
public sealed class DashboardSeeder(
DashboardDbContext dbContext,
ProgressAggregationService aggregationService)
{
private static readonly string[] ProcessNames = ["备料齐套", "生产装配", "检验入库"];
private static readonly OrderBlueprint[] Orders =
[
new("MO-260708-001", "青峪口尾水单向门机 2×160/10", 1200, "总装一线", "周敏", 68, 4.3, "industrial-pc"),
new("MO-260708-002", "电站尾水2×160kN/100kN单向门式启闭机", 860, "总装二线", "李澈", 82, 2.8, "workstation"),
new("MO-260708-003", "主起升机构", 420, "高配小批线", "陈嘉", 54, -1.2, "workstation"),
new("MO-260708-004", "100kN回转吊", 1500, "总装三线", "罗岚", 43, -3.1, "aio"),
new("MO-260708-005", "门架", 2100, "柔性装配线", "宋一", 76, 1.6, "industrial-pc"),
new("MO-260708-006", "大车行走机构", 640, "精密装配线", "韩策", 61, -0.8, "edge-box"),
];
private static readonly PartBlueprint[] Parts =
[
new("主起升机构", "self_made", "重装车间", "自产工段", "fixture",
[
new("卷扬机构", "self_made", "起升装配组", "自产工段", "chassis"),
new("滑轮组", "outsourced", "机加协作组", "东方重工协作厂", "thermal"),
new("制动装置", "purchased", "采购一组", "华东液压", "power"),
]),
new("100kN回转吊", "outsourced", "回转装配组", "西南重装", "chassis",
[
new("回转支承", "purchased", "采购二组", "洛轴供应链", "chassis"),
new("起升卷筒", "self_made", "卷筒制造组", "自产工段", "fixture"),
new("吊钩组", "outsourced", "外协质检组", "川重锻造", "generic"),
]),
new("门架", "self_made", "结构车间", "自产工段", "mainboard",
[
new("主梁结构", "self_made", "铆焊一组", "自产工段", "mainboard"),
new("支腿结构", "self_made", "铆焊二组", "自产工段", "mainboard"),
new("连接平台", "outsourced", "结构外协组", "青峪钢构", "generic"),
]),
new("大车行走机构", "self_made", "行走机构组", "自产工段", "chassis",
[
new("行走台车", "self_made", "台车装配组", "自产工段", "chassis"),
new("轨道夹持器", "purchased", "采购三组", "水工制动", "fixture"),
new("驱动减速机", "purchased", "传动采购组", "国机传动", "power"),
]),
new("门机电气控制系统", "purchased", "电气车间", "东方电控", "power",
[
new("PLC控制柜", "self_made", "电控装配组", "自产工段", "power"),
new("电缆卷筒", "outsourced", "电缆协作组", "长江电缆", "cable"),
new("限位与监测装置", "purchased", "仪控采购组", "水工智控", "chip"),
]),
];
public async Task SeedAsync(CancellationToken cancellationToken)
{
if (await dbContext.ProductionOrders.AnyAsync(cancellationToken))
{
return;
}
var lines = Orders
.Select((order, index) => new ProductionLine
{
Id = Guid.NewGuid(),
Code = $"LINE-{index + 1:00}",
Name = order.LineName,
Owner = order.Owner,
})
.ToList();
dbContext.ProductionLines.AddRange(lines);
dbContext.Partners.AddRange(
new Partner { Id = Guid.NewGuid(), Code = "SUP-001", Name = "东方重工协作厂", PartnerType = "outsourcer" },
new Partner { Id = Guid.NewGuid(), Code = "SUP-002", Name = "华东液压", PartnerType = "supplier" },
new Partner { Id = Guid.NewGuid(), Code = "SUP-003", Name = "水工智控", PartnerType = "supplier" });
var now = DateTimeOffset.UtcNow;
for (var orderIndex = 0; orderIndex < Orders.Length; orderIndex++)
{
var blueprint = Orders[orderIndex];
var order = new ProductionOrder
{
Id = Guid.NewGuid(),
Code = blueprint.Code,
ProductName = blueprint.ProductName,
DataSource = "demo",
BatchQty = blueprint.BatchQty,
RequiredQty = blueprint.BatchQty,
PlannedStart = new DateTime(2026, 7, 1).AddDays(orderIndex % 4),
PlannedEnd = new DateTime(2026, 7, 14).AddDays(orderIndex),
LineId = lines[orderIndex].Id,
LineName = blueprint.LineName,
Owner = blueprint.Owner,
DailyDelta = blueprint.DailyDelta,
ThumbnailKey = blueprint.ThumbnailKey,
CreatedAt = now.AddDays(-10 - orderIndex),
UpdatedAt = now,
RowVersion = Guid.NewGuid().ToByteArray(),
};
var nodes = BuildNodes(order, blueprint, orderIndex, now);
aggregationService.Recalculate(order, nodes);
order.PlanAchievement = Math.Clamp(order.Progress + 8 - order.DelayDays * 1.5, 0, 120);
dbContext.ProductionOrders.Add(order);
dbContext.ProductionNodes.AddRange(nodes);
dbContext.ProductionTrendPoints.AddRange(BuildTrend(order, orderIndex, now));
dbContext.RiskEvents.AddRange(BuildRiskEvents(order, nodes, now));
}
await dbContext.SaveChangesAsync(cancellationToken);
}
private static List<ProductionNode> BuildNodes(
ProductionOrder order,
OrderBlueprint blueprint,
int orderIndex,
DateTimeOffset now)
{
var nodes = new List<ProductionNode>();
var root = CreateNode(
order,
null,
"ROOT",
order.ProductName,
0,
"order",
"self_made",
order.BatchQty,
0,
order.Owner,
"东方水利",
blueprint.ThumbnailKey,
0,
order.PlannedStart!.Value,
order.PlannedEnd!.Value,
now);
nodes.Add(root);
for (var partIndex = 0; partIndex < Parts.Length; partIndex++)
{
var part = Parts[partIndex];
var partNode = CreateNode(
order,
root.Id,
$"P-{partIndex + 1:00}",
part.Name,
1,
"part",
part.SupplyType,
order.BatchQty,
partIndex,
part.Owner,
part.Vendor,
part.VisualKey,
partIndex,
order.PlannedStart!.Value.AddDays(partIndex),
order.PlannedEnd!.Value.AddDays(partIndex - Parts.Length),
now);
nodes.Add(partNode);
for (var componentIndex = 0; componentIndex < part.Components.Length; componentIndex++)
{
var component = part.Components[componentIndex];
var componentNode = CreateNode(
order,
partNode.Id,
$"C-{partIndex + 1:00}-{componentIndex + 1:00}",
component.Name,
2,
"component",
component.SupplyType,
order.BatchQty,
componentIndex,
component.Owner,
component.Vendor,
component.VisualKey,
componentIndex,
partNode.PlannedStart!.Value.AddDays(componentIndex),
partNode.PlannedEnd!.Value.AddDays(componentIndex - 2),
now);
nodes.Add(componentNode);
for (var processIndex = 0; processIndex < ProcessNames.Length; processIndex++)
{
var signature = orderIndex * 17 + partIndex * 11 + componentIndex * 7 + processIndex * 5;
var progress = Math.Clamp(blueprint.BaseProgress + signature % 29 - 14 - processIndex * 4, 3, 100);
var critical = signature % 13 == 0;
var warning = !critical && (signature % 5 == 0 || progress < 45);
var delayDays = critical ? 3 + signature % 4 : warning ? 1 : 0;
var status = progress >= 100
? "done"
: critical && processIndex == 2
? "blocked"
: delayDays > 0
? "delayed"
: "in_progress";
var risk = critical ? "critical" : warning ? "warning" : "normal";
var requiredQty = order.BatchQty;
var completedQty = (int)Math.Round(requiredQty * progress / 100d, MidpointRounding.AwayFromZero);
var processNode = CreateNode(
order,
componentNode.Id,
$"OP-{(processIndex + 1) * 10:00}",
$"{component.Name} - {ProcessNames[processIndex]}",
3,
"process",
component.SupplyType,
requiredQty,
processIndex,
component.Owner,
component.Vendor,
processIndex == 2 ? "package" : component.VisualKey,
processIndex,
componentNode.PlannedStart!.Value.AddDays(processIndex),
componentNode.PlannedEnd!.Value.AddDays(processIndex - 2),
now);
processNode.CompletedQty = completedQty;
processNode.DefectQty = Math.Min(completedQty, risk == "critical" ? Math.Max(1, completedQty / 50) : completedQty / 250);
processNode.Progress = progress;
processNode.Status = status;
processNode.RiskLevel = risk;
processNode.DelayDays = delayDays;
processNode.DelayReason = DelayReason(risk, component.SupplyType, ProcessNames[processIndex]);
processNode.ActualStart = progress > 0 ? processNode.PlannedStart : null;
processNode.ActualEnd = progress >= 100 ? processNode.PlannedEnd : null;
nodes.Add(processNode);
}
}
}
return nodes;
}
private static ProductionNode CreateNode(
ProductionOrder order,
Guid? parentId,
string code,
string name,
int level,
string nodeType,
string supplyType,
int requiredQty,
int codeIndex,
string owner,
string vendor,
string visualKey,
int sortOrder,
DateTime plannedStart,
DateTime plannedEnd,
DateTimeOffset now)
{
return new ProductionNode
{
Id = Guid.NewGuid(),
OrderId = order.Id,
ParentNodeId = parentId,
Code = code,
OperationCode = code,
MaterialCode = $"MAT-{order.Code[^3..]}-{level}{codeIndex + 1:00}",
Name = name,
Level = level,
NodeType = nodeType,
SupplyType = supplyType,
RequiredQty = requiredQty,
Owner = owner,
Vendor = vendor,
StationName = $"{owner}-{sortOrder + 1}",
VisualKey = visualKey,
SortOrder = sortOrder,
PlannedStart = plannedStart,
PlannedEnd = plannedEnd,
UpdatedAt = now,
RowVersion = Guid.NewGuid().ToByteArray(),
};
}
private static IEnumerable<ProductionTrendPoint> BuildTrend(
ProductionOrder order,
int orderIndex,
DateTimeOffset now)
{
for (var index = 0; index < 12; index++)
{
var completion = Math.Clamp(order.Progress - (11 - index) * 2 + (index + orderIndex) % 3, 0, 100);
var actual = (int)Math.Round(order.RequiredQty * completion / 100d);
var planned = Math.Max(actual, (int)Math.Round(order.RequiredQty * Math.Min(100, completion + 8) / 100d));
yield return new ProductionTrendPoint
{
Id = Guid.NewGuid(),
OrderId = order.Id,
SampleTime = now.AddMinutes((index - 11) * 8),
Completion = completion,
Risk = Math.Max(1, order.DelayDays + 11 - index),
PlannedQty = planned,
ActualQty = actual,
Achievement = planned == 0 ? 0 : Math.Min(120, actual * 100d / planned),
};
}
}
private static IEnumerable<RiskEvent> BuildRiskEvents(
ProductionOrder order,
IReadOnlyCollection<ProductionNode> nodes,
DateTimeOffset now)
{
return nodes
.Where(node => node.RiskLevel != "normal" || node.DelayDays > 0)
.OrderByDescending(node => node.RiskLevel == "critical")
.ThenByDescending(node => node.DelayDays)
.Take(8)
.Select((node, index) => new RiskEvent
{
Id = Guid.NewGuid(),
OrderId = order.Id,
NodeId = node.Id,
OrderCode = order.Code,
NodeName = node.Name,
RiskLevel = node.RiskLevel,
Message = string.IsNullOrWhiteSpace(node.DelayReason)
? $"计划偏差 {Math.Max(1, node.DelayDays)} 天,需要复核节拍"
: node.DelayReason,
HandlingStatus = "processing",
OccurredAt = now.AddMinutes(-index),
});
}
private static string DelayReason(string riskLevel, string supplyType, string processName)
{
if (riskLevel == "normal")
{
return string.Empty;
}
return supplyType switch
{
"outsourced" => $"{processName}外协回货节拍低于计划",
"purchased" => $"{processName}供应到料存在批次差异",
_ => $"{processName}工序良率波动,需要复检",
};
}
private sealed record OrderBlueprint(
string Code,
string ProductName,
int BatchQty,
string LineName,
string Owner,
int BaseProgress,
double DailyDelta,
string ThumbnailKey);
private sealed record PartBlueprint(
string Name,
string SupplyType,
string Owner,
string Vendor,
string VisualKey,
ComponentBlueprint[] Components);
private sealed record ComponentBlueprint(
string Name,
string SupplyType,
string Owner,
string Vendor,
string VisualKey);
}