|
|
@@ -9,13 +9,18 @@ namespace Admin.NET.Plugin.AiDOP.Manufacturing;
|
|
|
/// S6 生产执行 — KPI 计算与刷新转换服务。
|
|
|
/// 双模式:读本地标准层 mdp_std_t8_kc_*(由 T8BaseInboundMdpSyncService 从 T8 贴源→标准),不再直连 T8。
|
|
|
/// 计算逻辑沿用方老师 v5.4 KPI J 列口径(等价改写为 MySQL 读 std)。
|
|
|
-/// 包含 KPI:S6_L1_001 工单制造满足率 / S6_L1_002 工单制造人效。
|
|
|
+/// 口径归位(S6-L1-KPI-CONTRACT-RESOLUTION-1,方案 B):L1=订单级(待 Phase 2 实现),
|
|
|
+/// L2=工单级——本服务产出 **工单** 指标写 L2:
|
|
|
+/// 工单制造满足率→S6_L2_002(l2_day),工单制造人效→S6_L2_003(l2_day);不再写 S6_L1_001/002。
|
|
|
+/// 最终聚合经 KpiCalcDispatcher(LEGACY_CODE/CONFIG_SQL),数据准备(dwd)不变。
|
|
|
/// </summary>
|
|
|
public class S6MdpSyncTransformService : ITransient
|
|
|
{
|
|
|
private readonly ISqlSugarClient _db;
|
|
|
private readonly SysNoticeService _sysNoticeService;
|
|
|
private readonly ILogger<S6MdpSyncTransformService> _logger;
|
|
|
+ private readonly SmartOps.KpiCalcDispatcher _kpiCalcDispatcher;
|
|
|
+ private const string L2ValueTable = "ado_s9_kpi_value_l2_day";
|
|
|
private const string JobCode = "S6_MDP_SYNC_TRANSFORM";
|
|
|
private const string JobName = "S6 生产执行 MDP 同步与转换";
|
|
|
private const string ModuleCode = "S6";
|
|
|
@@ -26,11 +31,13 @@ public class S6MdpSyncTransformService : ITransient
|
|
|
public S6MdpSyncTransformService(
|
|
|
ISqlSugarClient db,
|
|
|
SysNoticeService sysNoticeService,
|
|
|
- ILogger<S6MdpSyncTransformService> logger)
|
|
|
+ ILogger<S6MdpSyncTransformService> logger,
|
|
|
+ SmartOps.KpiCalcDispatcher kpiCalcDispatcher)
|
|
|
{
|
|
|
_db = db;
|
|
|
_sysNoticeService = sysNoticeService;
|
|
|
_logger = logger;
|
|
|
+ _kpiCalcDispatcher = kpiCalcDispatcher;
|
|
|
}
|
|
|
|
|
|
public async Task<S6MdpSyncTransformResult> RunFullAsync(
|
|
|
@@ -66,11 +73,12 @@ public class S6MdpSyncTransformService : ITransient
|
|
|
result.StageRows = 0;
|
|
|
result.StandardRows = 0;
|
|
|
|
|
|
- var sub22 = await BuildS6L1001WorkOrderMfgFulfillmentAsync(batchId, now, option, cancellationToken);
|
|
|
- result.MergeSub("S6_L1_001", sub22);
|
|
|
+ // 口径归位:工单指标写 L2(S6_L2_002 满足率 / S6_L2_003 人效);L1 订单级待 Phase 2。
|
|
|
+ var sub22 = await BuildS6L2002WorkOrderMfgFulfillmentAsync(batchId, now, option, normalizedTrigger, cancellationToken);
|
|
|
+ result.MergeSub("S6_L2_002", sub22);
|
|
|
|
|
|
- var sub23 = await BuildS6L1002WorkOrderMfgEfficiencyAsync(batchId, now, option, cancellationToken);
|
|
|
- result.MergeSub("S6_L1_002", sub23);
|
|
|
+ var sub23 = await BuildS6L2003WorkOrderMfgEfficiencyAsync(batchId, now, option, normalizedTrigger, cancellationToken);
|
|
|
+ result.MergeSub("S6_L2_003", sub23);
|
|
|
|
|
|
await MarkTransformRunSuccessAsync(runLogId, now, result);
|
|
|
return result;
|
|
|
@@ -84,9 +92,9 @@ public class S6MdpSyncTransformService : ITransient
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
- /// <summary>S6_L1_001 工单制造满足率 = 计划完工时间内累计报工 / 工单计划生产数量。</summary>
|
|
|
- private async Task<KpiBuildSubResult> BuildS6L1001WorkOrderMfgFulfillmentAsync(
|
|
|
- string batchId, DateTime now, S6MdpRefreshOption option, CancellationToken ct)
|
|
|
+ /// <summary>工单制造满足率 = 计划完工时间内累计报工 / 工单计划生产数量。归位 L2:写 S6_L2_002。</summary>
|
|
|
+ private async Task<KpiBuildSubResult> BuildS6L2002WorkOrderMfgFulfillmentAsync(
|
|
|
+ string batchId, DateTime now, S6MdpRefreshOption option, string triggerType, CancellationToken ct)
|
|
|
{
|
|
|
var sub = new KpiBuildSubResult();
|
|
|
|
|
|
@@ -145,17 +153,25 @@ ON DUPLICATE KEY UPDATE
|
|
|
}
|
|
|
sub.DwdRows = dwdAffected;
|
|
|
|
|
|
- decimal? metricValue = rateList.Count > 0
|
|
|
+ // 数据准备(dwd)已完成。最终聚合交分发器(日 KPI:period 复用 BizDate,SQL 按 biz_date 圈选)。
|
|
|
+ decimal? legacyValue = rateList.Count > 0
|
|
|
? Math.Round(rateList.Average() * 100m, 4) // 百分号
|
|
|
: null;
|
|
|
- sub.KpiRows = await UpsertKpiValueAsync("S6_L1_001", option.BizDate, metricValue, now, option);
|
|
|
- sub.DenominatorStatus = rateList.Count > 0 ? "OK" : "NO_VALID_WORK_ORDER";
|
|
|
+ var legacyDenom = rateList.Count > 0 ? "OK" : "NO_VALID_WORK_ORDER";
|
|
|
+ var dispatch = await _kpiCalcDispatcher.DispatchAsync(
|
|
|
+ "S6_L2_002", ModuleCode, option.TargetTenantId, option.TargetFactoryId,
|
|
|
+ option.BizDate, option.BizDate, option.BizDate, option.SourceZtid,
|
|
|
+ batchId, triggerType, legacyValue, legacyDenom, ct);
|
|
|
+ sub.KpiRows = dispatch.ShouldUpsert
|
|
|
+ ? await UpsertKpiValueAsync("S6_L2_002", option.BizDate, dispatch.MetricValue, now, option, L2ValueTable)
|
|
|
+ : 0;
|
|
|
+ sub.DenominatorStatus = dispatch.DenominatorStatus;
|
|
|
return sub;
|
|
|
}
|
|
|
|
|
|
- /// <summary>S6_L1_002 工单制造人效 = 完成制造工单数(lbs=生产入库 AND (slzx>=sl OR gdyn=1)) / count(gw=生产)。</summary>
|
|
|
- private async Task<KpiBuildSubResult> BuildS6L1002WorkOrderMfgEfficiencyAsync(
|
|
|
- string batchId, DateTime now, S6MdpRefreshOption option, CancellationToken ct)
|
|
|
+ /// <summary>工单制造人效 = 完成制造工单数(lbs=生产入库 AND (slzx>=sl OR gdyn=1)) / count(gw=生产)。归位 L2:写 S6_L2_003。</summary>
|
|
|
+ private async Task<KpiBuildSubResult> BuildS6L2003WorkOrderMfgEfficiencyAsync(
|
|
|
+ string batchId, DateTime now, S6MdpRefreshOption option, string triggerType, CancellationToken ct)
|
|
|
{
|
|
|
var sub = new KpiBuildSubResult();
|
|
|
|
|
|
@@ -225,22 +241,30 @@ ON DUPLICATE KEY UPDATE
|
|
|
new SugarParameter("@now", now));
|
|
|
sub.DwdRows = dwdAffected;
|
|
|
|
|
|
- sub.KpiRows = await UpsertKpiValueAsync("S6_L1_002", option.MonthlyPeriodEnd, efficiency, now, option);
|
|
|
+ // 月度 KPI 最终聚合交分发器;bizDate=月末、period=当月窗口;legacyValue=efficiency、legacyDenom 保留 NO_HEADCOUNT/NO_NUMERATOR。
|
|
|
+ var dispatch = await _kpiCalcDispatcher.DispatchAsync(
|
|
|
+ "S6_L2_003", ModuleCode, option.TargetTenantId, option.TargetFactoryId,
|
|
|
+ option.MonthlyPeriodEnd, option.MonthlyPeriodStart, option.MonthlyPeriodEnd, option.SourceZtid,
|
|
|
+ batchId, triggerType, efficiency, denomStatus, ct);
|
|
|
+ sub.KpiRows = dispatch.ShouldUpsert
|
|
|
+ ? await UpsertKpiValueAsync("S6_L2_003", option.MonthlyPeriodEnd, dispatch.MetricValue, now, option, L2ValueTable)
|
|
|
+ : 0;
|
|
|
+ sub.DenominatorStatus = dispatch.DenominatorStatus;
|
|
|
return sub;
|
|
|
}
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
- private async Task<int> UpsertKpiValueAsync(string metricCode, DateTime bizDate, decimal? metricValue, DateTime now, S6MdpRefreshOption option)
|
|
|
+ private async Task<int> UpsertKpiValueAsync(string metricCode, DateTime bizDate, decimal? metricValue, DateTime now, S6MdpRefreshOption option, string valueTable = "ado_s9_kpi_value_l1_day")
|
|
|
{
|
|
|
// 沿用 S3 UpsertS3KpiValueAsync 范式:先查现存行 → UPDATE;不存在 → SELECT MAX(id)+1 显式生成 id 后 INSERT。
|
|
|
- // ado_s9_kpi_value_l1_day.id 为手工分配主键(无 AUTO_INCREMENT),必须显式 set;
|
|
|
- // metric_value 允许 NULL(分母缺失不得伪装真实 0)。
|
|
|
- // FIX-2:截断时分秒(月度 KPI 入参可能为 YYYY-MM-DD 23:59:59),保证 SELECT WHERE biz_date=@BizDate 与 DB date 列匹配,避免重复 INSERT。
|
|
|
- // FIX-1:tenant_id/factory_id 取自 option,默认仍为 1300000000001/1,不破坏 Demo。
|
|
|
+ // 值表 id 为手工分配主键(无 AUTO_INCREMENT),必须显式 set;metric_value 允许 NULL(分母缺失不得伪装真实 0)。
|
|
|
+ // valueTable:L1 KPI 写 ado_s9_kpi_value_l1_day;L2(本服务工单指标 S6_L2_002/003)写 ado_s9_kpi_value_l2_day。
|
|
|
+ // 表名为受控常量(非用户输入),可安全内插;各表 id 序列独立,MAX(id) 取目标表。
|
|
|
+ // FIX-2:截断时分秒(月度 KPI 入参可能为 YYYY-MM-DD 23:59:59),保证 SELECT WHERE biz_date=@BizDate 与 DB date 列匹配。
|
|
|
bizDate = bizDate.Date;
|
|
|
var existingId = await _db.Ado.GetLongAsync(
|
|
|
- "SELECT IFNULL((SELECT id FROM ado_s9_kpi_value_l1_day WHERE tenant_id=@TenantId AND factory_id=@FactoryId " +
|
|
|
+ $"SELECT IFNULL((SELECT id FROM {valueTable} WHERE tenant_id=@TenantId AND factory_id=@FactoryId " +
|
|
|
"AND module_code=@ModuleCode AND metric_code=@MetricCode AND biz_date=@BizDate AND is_deleted=0 " +
|
|
|
"ORDER BY id LIMIT 1), 0)",
|
|
|
new List<SugarParameter>
|
|
|
@@ -255,7 +279,7 @@ ON DUPLICATE KEY UPDATE
|
|
|
if (existingId > 0)
|
|
|
{
|
|
|
return await _db.Ado.ExecuteCommandAsync(
|
|
|
- "UPDATE ado_s9_kpi_value_l1_day SET metric_value=@MetricValue, calc_time=@Now, " +
|
|
|
+ $"UPDATE {valueTable} SET metric_value=@MetricValue, calc_time=@Now, " +
|
|
|
"update_time=@Now, is_deleted=0, is_active=1 WHERE id=@Id",
|
|
|
new SugarParameter("@MetricValue", metricValue),
|
|
|
new SugarParameter("@Now", now),
|
|
|
@@ -263,9 +287,9 @@ ON DUPLICATE KEY UPDATE
|
|
|
}
|
|
|
|
|
|
var nextId = await _db.Ado.GetLongAsync(
|
|
|
- "SELECT COALESCE(MAX(id), 0) + 1 FROM ado_s9_kpi_value_l1_day");
|
|
|
- return await _db.Ado.ExecuteCommandAsync(@"
|
|
|
-INSERT INTO ado_s9_kpi_value_l1_day
|
|
|
+ $"SELECT COALESCE(MAX(id), 0) + 1 FROM {valueTable}");
|
|
|
+ return await _db.Ado.ExecuteCommandAsync($@"
|
|
|
+INSERT INTO {valueTable}
|
|
|
(id, tenant_id, org_id, company_id, factory_id, status, biz_date,
|
|
|
create_time, update_time, is_deleted, is_active,
|
|
|
module_code, metric_code, metric_value, calc_time)
|