using Admin.NET.Core;
using Admin.NET.Plugin.AiDOP.Entity;
using Admin.NET.Plugin.AiDOP.FinishedWarehouse;
using Admin.NET.Plugin.AiDOP.Infrastructure;
using Admin.NET.Plugin.AiDOP.Manufacturing;
using Admin.NET.Plugin.AiDOP.MaterialWarehouse;
using Admin.NET.Plugin.AiDOP.Order;
using Admin.NET.Plugin.AiDOP.ProcurementExecution;
using Admin.NET.Plugin.AiDOP.Production;
using Admin.NET.Plugin.AiDOP.Supply;
using Admin.NET.Plugin.AiDOP.Service.S8;
using Admin.NET.Plugin.AiDOP.SmartOps;
using SqlSugar;
namespace Admin.NET.Plugin.AiDOP.Controllers;
[ApiController]
[Route("api/[controller]")]
[NonUnify]
public partial class AidopKanbanController : ControllerBase
{
private readonly ISqlSugarClient _db;
private readonly S1MdpSyncTransformService _s1MdpSyncTransformService;
private readonly S2MdpSyncTransformService _s2MdpSyncTransformService;
private readonly S3MdpSyncTransformService _s3MdpSyncTransformService;
private readonly MaterialWarehouse.IqcInspectionMdpSyncService _iqcInspectionMdpSyncService;
private readonly PurchaseOrderCompletionMdpSyncService _poCompletionMdpSyncService;
private readonly S4MdpSyncTransformService _s4MdpSyncTransformService;
private readonly S5MdpSyncTransformService _s5MdpSyncTransformService;
private readonly S6MdpSyncTransformService _s6MdpSyncTransformService;
private readonly S7MdpSyncTransformService _s7MdpSyncTransformService;
private readonly SmartOpsKpiAtomicQueryService _atomicQuery;
private readonly OutsourceIssueMdpSyncService _outsourceIssueMdpSyncService;
private readonly PurchaseReceiptMdpSyncService _purchaseReceiptMdpSyncService;
private readonly ProductionReturnMdpSyncService _productionReturnMdpSyncService;
private readonly ProductionIssueMdpSyncService _productionIssueMdpSyncService;
private readonly ProductionReceiptMdpSyncService _productionReceiptMdpSyncService;
private readonly IpqcInspectionMdpSyncService _ipqcInspectionMdpSyncService;
private readonly ReportWorkMdpSyncService _reportWorkMdpSyncService;
private readonly SmartOpsTrustedScopeResolver _trustedScope;
private readonly SmartDiagnosisEvidenceService _evidence;
private readonly S8KpiSnapshotService _s8KpiSnapshot;
public AidopKanbanController(
ISqlSugarClient db,
S1MdpSyncTransformService s1MdpSyncTransformService,
S2MdpSyncTransformService s2MdpSyncTransformService,
S3MdpSyncTransformService s3MdpSyncTransformService,
MaterialWarehouse.IqcInspectionMdpSyncService iqcInspectionMdpSyncService,
PurchaseOrderCompletionMdpSyncService poCompletionMdpSyncService,
S4MdpSyncTransformService s4MdpSyncTransformService,
S5MdpSyncTransformService s5MdpSyncTransformService,
S6MdpSyncTransformService s6MdpSyncTransformService,
S7MdpSyncTransformService s7MdpSyncTransformService,
SmartOpsKpiAtomicQueryService atomicQuery,
OutsourceIssueMdpSyncService outsourceIssueMdpSyncService,
PurchaseReceiptMdpSyncService purchaseReceiptMdpSyncService,
ProductionReturnMdpSyncService productionReturnMdpSyncService,
ProductionIssueMdpSyncService productionIssueMdpSyncService,
ProductionReceiptMdpSyncService productionReceiptMdpSyncService,
IpqcInspectionMdpSyncService ipqcInspectionMdpSyncService,
ReportWorkMdpSyncService reportWorkMdpSyncService,
SmartOpsTrustedScopeResolver trustedScope,
SmartDiagnosisEvidenceService evidence,
S8KpiSnapshotService s8KpiSnapshot)
{
_db = db;
_s1MdpSyncTransformService = s1MdpSyncTransformService;
_s2MdpSyncTransformService = s2MdpSyncTransformService;
_s3MdpSyncTransformService = s3MdpSyncTransformService;
_iqcInspectionMdpSyncService = iqcInspectionMdpSyncService;
_poCompletionMdpSyncService = poCompletionMdpSyncService;
_s4MdpSyncTransformService = s4MdpSyncTransformService;
_s5MdpSyncTransformService = s5MdpSyncTransformService;
_s6MdpSyncTransformService = s6MdpSyncTransformService;
_s7MdpSyncTransformService = s7MdpSyncTransformService;
_atomicQuery = atomicQuery;
_outsourceIssueMdpSyncService = outsourceIssueMdpSyncService;
_purchaseReceiptMdpSyncService = purchaseReceiptMdpSyncService;
_productionReturnMdpSyncService = productionReturnMdpSyncService;
_productionIssueMdpSyncService = productionIssueMdpSyncService;
_productionReceiptMdpSyncService = productionReceiptMdpSyncService;
_ipqcInspectionMdpSyncService = ipqcInspectionMdpSyncService;
_reportWorkMdpSyncService = reportWorkMdpSyncService;
_trustedScope = trustedScope;
_evidence = evidence;
_s8KpiSnapshot = s8KpiSnapshot;
}
///
/// 已弃用。前端零调用点,九宫格实际走 home-grid/{moduleCode}()。
/// 本端点用全局 MAX(biz_date) 卡所有模块,没有该日数据的模块会被整段筛掉,不可复用、不可接回。
///
[Obsolete("前端零调用点。九宫格走 home-grid/{moduleCode}。全局 MAX(biz_date) 会筛掉其它模块,不可复用。")]
[HttpGet("home-l1")]
public async Task GetHomeL1([FromQuery] long factoryId = 1)
{
var tenantId = AidopTenantHelper.GetTenantId(HttpContext);
const string sql = """
SELECT v.module_code AS ModuleCode, v.metric_code AS MetricCode,
v.metric_value AS MetricValue, v.target_value AS TargetValue,
v.status_color AS StatusColor, v.trend_flag AS TrendFlag,
k.Direction, k.YellowThreshold, k.RedThreshold
FROM ado_s9_kpi_value_l1_day v
LEFT JOIN ado_smart_ops_kpi_master k
ON k.MetricCode = v.metric_code COLLATE utf8mb4_general_ci AND k.TenantId = v.tenant_id
WHERE v.tenant_id=@tenantId AND v.factory_id=@factoryId AND v.is_deleted=0
AND v.biz_date = (SELECT MAX(biz_date) FROM ado_s9_kpi_value_l1_day
WHERE tenant_id=@tenantId AND factory_id=@factoryId AND is_deleted=0)
ORDER BY v.module_code
""";
var raw = await _db.Ado.SqlQueryAsync(sql, new { tenantId, factoryId });
var rows = raw.Select(r => new HomeL1Dto
{
ModuleCode = r.ModuleCode,
MetricCode = r.MetricCode,
MetricValue = r.MetricValue,
TargetValue = r.TargetValue,
TrendFlag = r.TrendFlag,
StatusColor = AidopS4KpiMerge.AchievementLevel(
r.MetricValue, r.TargetValue,
r.Direction ?? "higher_is_better",
r.YellowThreshold, r.RedThreshold)
}).ToList();
return Ok(rows);
}
[HttpGet("s8-alerts")]
public async Task GetS8Alerts(
[FromQuery] long factoryId = 1,
[FromQuery] string? dateStart = null,
[FromQuery] string? dateEnd = null,
[FromQuery] string? product = null,
[FromQuery] string? orderNo = null,
[FromQuery] string? productionLine = null)
{
var tenantId = AidopTenantHelper.GetTenantId(HttpContext);
var filter = SmartOps.SmartOpsDashboardFilter.FromQuery(
dateStart, dateEnd, product, orderNo, productionLine, null, null, null, null, null, null, null, null);
var dimSql = BuildS8AlertTextFilterSql();
var sql = $"""
SELECT DATE_FORMAT(alert_time, '%H:%i') AS Time,
IFNULL(SUBSTRING_INDEX(metric_code, '_', 1), 'S8') AS Module,
IFNULL(message, '异常告警') AS Message,
IFNULL(level_code, 'medium') AS LevelCode
FROM ado_s8_alert_record
WHERE tenant_id=@tenantId AND factory_id=@factoryId AND is_deleted=0
AND (@ds IS NULL OR alert_time >= @ds)
AND (@de IS NULL OR alert_time < DATE_ADD(@de, INTERVAL 1 DAY))
{dimSql}
ORDER BY alert_time DESC
LIMIT 6
""";
var rows = await _db.Ado.SqlQueryAsync(sql, new
{
tenantId,
factoryId,
ds = filter.DateStart,
de = filter.DateEnd,
product = filter.Product,
orderNo = filter.OrderNo,
productionLine = filter.ProductionLine
});
return Ok(rows.Select(x => new
{
time = x.Time,
module = x.Module,
message = x.Message,
level = x.LevelCode,
levelLabel = x.LevelCode switch
{
"critical" => "严重",
"high" => "高",
"medium" => "中",
_ => "一般"
}
}));
}
///
/// S8-DASHBOARD-P2-SEMANTICS-1(P2-B):S8 看板预警计数。
///
/// 为什么要单开一个端点:s8-alerts 是「最近 6 条」列表,带 LIMIT 6
/// 且 SQL 里没有任何今日谓词。前端拿它的 length 当「今日预警数」,于是
/// ① 真实今日 0 条、表里存着几个月前的旧行时,卡片照样显示非 0;
/// ② 今日真有 7 条以上时,卡片永远封顶在 6。两个方向都错。
///
/// 窗口口径:未传日期 ⇒ [今日 00:00, 明日 00:00)(服务器自然日,
/// isToday=true);传了日期 ⇒ [dateStart, dateEnd + 1 天),isToday=false,
/// 由前端把标题从「今日预警数」切成「区间预警数」。一律半开区间,与 s8-alerts 同构。
///
/// 维度过滤复用 ,不另起口径。
///
[HttpGet("s8-alert-summary")]
public async Task GetS8AlertSummary(
[FromQuery] long factoryId = 1,
[FromQuery] string? dateStart = null,
[FromQuery] string? dateEnd = null,
[FromQuery] string? product = null,
[FromQuery] string? orderNo = null,
[FromQuery] string? productionLine = null)
{
var tenantId = AidopTenantHelper.GetTenantId(HttpContext);
var filter = SmartOps.SmartOpsDashboardFilter.FromQuery(
dateStart, dateEnd, product, orderNo, productionLine, null, null, null, null, null, null, null, null);
var isToday = !filter.DateStart.HasValue && !filter.DateEnd.HasValue;
var windowStart = (filter.DateStart ?? filter.DateEnd ?? DateTime.Today).Date;
var windowEnd = (filter.DateEnd ?? filter.DateStart ?? DateTime.Today).Date.AddDays(1);
if (windowEnd <= windowStart) windowEnd = windowStart.AddDays(1);
var dimSql = BuildS8AlertTextFilterSql();
var sql = $"""
SELECT COUNT(*) AS Total,
IFNULL(SUM(CASE WHEN level_code = 'critical' THEN 1 ELSE 0 END), 0) AS Critical
FROM ado_s8_alert_record
WHERE tenant_id=@tenantId AND factory_id=@factoryId AND is_deleted=0
AND alert_time >= @ws
AND alert_time < @we
{dimSql}
""";
var rows = await _db.Ado.SqlQueryAsync(sql, new
{
tenantId,
factoryId,
ws = windowStart,
we = windowEnd,
product = filter.Product,
orderNo = filter.OrderNo,
productionLine = filter.ProductionLine
});
var row = rows.FirstOrDefault();
return Ok(new
{
windowStart = windowStart.ToString("yyyy-MM-dd"),
windowEnd = windowEnd.AddDays(-1).ToString("yyyy-MM-dd"),
isToday,
total = row?.Total ?? 0,
critical = row?.Critical ?? 0
});
}
private sealed class S8AlertSummaryRow
{
public int Total { get; set; }
public int Critical { get; set; }
}
[HttpGet("s8-home-trend")]
public async Task GetS8HomeTrend(
[FromQuery] long factoryId = 1,
[FromQuery] string? dateStart = null,
[FromQuery] string? dateEnd = null,
[FromQuery] string? product = null,
[FromQuery] string? orderNo = null,
[FromQuery] string? productionLine = null)
{
var tenantId = AidopTenantHelper.GetTenantId(HttpContext);
var filter = SmartOps.SmartOpsDashboardFilter.FromQuery(
dateStart, dateEnd, product, orderNo, productionLine, null, null, null, null, null, null, null, null);
var end = (filter.DateEnd ?? DateTime.Today).Date;
var start = (filter.DateStart ?? end.AddDays(-6)).Date;
if ((end - start).TotalDays > 13) start = end.AddDays(-6);
var dimSql = BuildS8AlertTextFilterSql();
var sql = $"""
SELECT DATE(alert_time) AS BizDate, COUNT(*) AS Cnt
FROM ado_s8_alert_record
WHERE tenant_id=@tenantId AND factory_id=@factoryId AND is_deleted=0
AND alert_time >= @ds AND alert_time < DATE_ADD(@de, INTERVAL 1 DAY)
{dimSql}
GROUP BY DATE(alert_time)
ORDER BY BizDate
""";
var rows = await _db.Ado.SqlQueryAsync(sql, new
{
tenantId,
factoryId,
ds = start,
de = end,
product = filter.Product,
orderNo = filter.OrderNo,
productionLine = filter.ProductionLine
});
var byDate = rows.ToDictionary(x => x.BizDate.Date, x => x.Cnt);
var points = new List