using Admin.NET.Plugin.AiDOP.SmartOps;
namespace Admin.NET.Plugin.AiDOP.Service.S8;
///
/// S8 独立异常链汇总:把 ado_s8_exception 写成九宫格可读的 L1 日值。
/// 不把未闭环异常的处理时效伪装成 0。
///
public class S8KpiSnapshotService : ITransient
{
private const string ModuleCode = "S8";
private const string ValueTable = "ado_s9_kpi_value_l1_day";
private readonly ISqlSugarClient _db;
private readonly IKpiTargetResolver _kpiTargetResolver;
public S8KpiSnapshotService(ISqlSugarClient db, IKpiTargetResolver kpiTargetResolver)
{
_db = db;
_kpiTargetResolver = kpiTargetResolver;
}
public async Task RefreshAsync(long tenantId, long factoryId, DateTime? bizDate = null, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (tenantId <= 0 || factoryId <= 0 || factoryId == tenantId)
return 0;
var day = (bizDate ?? DateTime.Now).Date;
var now = DateTime.Now;
var count = await _db.Ado.GetIntAsync(
"""
SELECT COUNT(1)
FROM ado_s8_exception
WHERE tenant_id=@TenantId
AND IFNULL(is_deleted,0)=0
AND IFNULL(exception_code,'')<>''
AND (factory_id IS NULL OR factory_id=0 OR factory_id=@FactoryId
OR (@FactoryId IN (0,1) AND factory_id<>@TenantId))
""",
new SugarParameter("@TenantId", tenantId),
new SugarParameter("@FactoryId", factoryId));
var avgHours = await _db.Ado.GetDecimalAsync(
"""
SELECT ROUND(AVG(TIMESTAMPDIFF(MINUTE, created_at, closed_at) / 60), 4)
FROM ado_s8_exception
WHERE tenant_id=@TenantId
AND IFNULL(is_deleted,0)=0
AND UPPER(IFNULL(status,''))='CLOSED'
AND closed_at IS NOT NULL
AND closed_at >= created_at
AND (factory_id IS NULL OR factory_id=0 OR factory_id=@FactoryId
OR (@FactoryId IN (0,1) AND factory_id<>@TenantId))
""",
new SugarParameter("@TenantId", tenantId),
new SugarParameter("@FactoryId", factoryId));
var affected = await UpsertAsync("S8_L1_001", tenantId, factoryId, day, now, count);
affected += await UpsertAsync(
"S8_L1_002",
tenantId,
factoryId,
day,
now,
avgHours > 0 ? avgHours : null);
return affected;
}
private async Task UpsertAsync(string metricCode, long tenantId, long factoryId, DateTime bizDate, DateTime now, decimal? metricValue)
{
var snap = await _kpiTargetResolver.ResolveAsync(tenantId, factoryId, metricCode, ModuleCode, bizDate);
var existingId = await _db.Ado.GetLongAsync(
$"""
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
{
new("@TenantId", tenantId),
new("@FactoryId", factoryId),
new("@ModuleCode", ModuleCode),
new("@MetricCode", metricCode),
new("@BizDate", bizDate)
});
if (existingId > 0)
{
return await _db.Ado.ExecuteCommandAsync(
$"""
UPDATE {ValueTable}
SET metric_value=@MetricValue, target_value=@TargetValue,
target_config_id=@TargetConfigId, target_source=@TargetSource, target_resolved_at=@TargetResolvedAt,
calc_time=@Now, update_time=@Now, is_deleted=0, is_active=1
WHERE id=@Id
""",
new SugarParameter("@MetricValue", metricValue),
new SugarParameter("@TargetValue", KpiTargetSnapshotSql.ValueOrDbNull(snap)),
new SugarParameter("@TargetConfigId", KpiTargetSnapshotSql.ConfigIdOrDbNull(snap)),
new SugarParameter("@TargetSource", KpiTargetSnapshotSql.SourceOrDbNull(snap)),
new SugarParameter("@TargetResolvedAt", snap.ResolvedAt),
new SugarParameter("@Now", now),
new SugarParameter("@Id", existingId));
}
var nextId = await _db.Ado.GetLongAsync($"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, target_value, calc_time,
target_config_id, target_source, target_resolved_at)
VALUES
(@Id, @TenantId, NULL, NULL, @FactoryId, NULL, @BizDate,
@Now, @Now, 0, 1,
@ModuleCode, @MetricCode, @MetricValue, @TargetValue, @Now,
@TargetConfigId, @TargetSource, @TargetResolvedAt)
""",
new SugarParameter("@Id", nextId),
new SugarParameter("@TenantId", tenantId),
new SugarParameter("@FactoryId", factoryId),
new SugarParameter("@BizDate", bizDate),
new SugarParameter("@Now", now),
new SugarParameter("@ModuleCode", ModuleCode),
new SugarParameter("@MetricCode", metricCode),
new SugarParameter("@MetricValue", metricValue),
new SugarParameter("@TargetValue", KpiTargetSnapshotSql.ValueOrDbNull(snap)),
new SugarParameter("@TargetConfigId", KpiTargetSnapshotSql.ConfigIdOrDbNull(snap)),
new SugarParameter("@TargetSource", KpiTargetSnapshotSql.SourceOrDbNull(snap)),
new SugarParameter("@TargetResolvedAt", snap.ResolvedAt));
}
}