using Dapper; using GB5Shared.Connection; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ServerConfig; using GB5Shared.Telemetry; using Microsoft.Data.SqlClient; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Quartz; namespace TMSBLL.KpiPosting { // Mirrors FMBLL.Handlers.AssetActivitySchedulerJob's own tenant-iteration shape exactly -- the // one real, running, per-tenant scheduling pattern in this codebase (JobEngine/MJOBDEFINE is // confirmed unprovisioned in every host; this is NOT that). Runs nightly, once per active // tenant found in MSERVERCONFIG/MSERVER. [DisallowConcurrentExecution] public class TmsKpiPostingJob : IJob { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private const string TenantQuery = @" SELECT SERVERCONFIG1.CLIENTID AS ClientId, SERVERCONFIG1.DATABASENAME AS DatabaseName, SERVERCONFIG1.DATABASETYPE AS DbType, SERVERCONFIG1.CONNECTIONNAME AS ConnectionName FROM MSERVERCONFIG SERVERCONFIG1 JOIN MSERVER SERVER1 ON SERVERCONFIG1.SERVERID = SERVER1.SERVERID WHERE SERVERCONFIG1.STATUS = 1 AND SERVERCONFIG1.CONNECTIONNAME <> 'ACTIVITI'"; public TmsKpiPostingJob(IServiceScopeFactory scopeFactory, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; } public async System.Threading.Tasks.Task Execute(IJobExecutionContext context) { using var activity = GB5Trace.BeginSection("tms-kpi-posting-job"); var ct = context.CancellationToken; using var loadScope = _scopeFactory.CreateScope(); var appConnection = loadScope.ServiceProvider.GetRequiredService(); List tenants; try { tenants = (await LoadTenantsAsync(appConnection).ConfigureAwait(false)).ToList(); } catch (Exception ex) { _logger.LogError(ex, "TmsKpiPostingJob: failed to load tenant list from MSERVERCONFIG"); return; } _logger.LogInformation("TmsKpiPostingJob: posting TMS KPIs for {Count} active tenant(s)", tenants.Count); foreach (var tenant in tenants) { ct.ThrowIfCancellationRequested(); await RunForTenantAsync(tenant, ct).ConfigureAwait(false); } } private async System.Threading.Tasks.Task RunForTenantAsync(ServerConfigDTO tenant, CancellationToken ct) { using var scope = _scopeFactory.CreateScope(); var postingBll = scope.ServiceProvider.GetRequiredService(); var login = new LoginDTO { ClientId = tenant.ClientId, UserId = -1, DatabaseName = tenant.DatabaseName, IsSchedulerRun = 1 }; try { var result = await postingBll.PostAllTmsKpis(login, ct).ConfigureAwait(false); if (result is not null) _logger.LogInformation("TmsKpiPostingJob: {Result}", result); } catch (Exception ex) { GB5Trace.MarkFailed("tms-kpi-posting-job-tenant-failed", ex); _logger.LogError(ex, "TmsKpiPostingJob failed for tenant {ClientId} ({DatabaseName})", tenant.ClientId, tenant.DatabaseName); } } private static async Task> LoadTenantsAsync(IApplicationConnection appConnection) { var systemConn = await appConnection.Gb5SystemConnectionString().ConfigureAwait(false); using var conn = new SqlConnection(systemConn); return await conn.QueryAsync(TenantQuery).ConfigureAwait(false); } } }