using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace AnalyticsBLL.OEE { // Nightly OEE recalculation job. // Runs once at startup (after 1-minute delay), then every 24 hours at 01:00 UTC. // Uses IServiceScopeFactory because IOEECalculationEngine is Scoped — never store Scoped // services directly in a Singleton-lifetime BackgroundService. // // LoginDTO NOTE: each tenant needs its own LoginDTO for DB connection. // Production integration: inject ITenantConnectionProvider (from GB5Framework) and // iterate active tenants — each provides a LoginDTO with correct DatabaseName + ClientId. // For now, this skeleton demonstrates the pattern; wire real tenants in the next sprint. public class OEENightlyJob : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; public OEENightlyJob(IServiceScopeFactory scopeFactory, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { // Initial delay — let app fully start before first run await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken).ConfigureAwait(false); while (!stoppingToken.IsCancellationRequested) { await RunOEEForAllTenantsAsync(stoppingToken).ConfigureAwait(false); // Sleep until next 01:00 UTC var now = DateTime.UtcNow; var next01 = now.Date.AddDays(now.Hour >= 1 ? 1 : 0).AddHours(1); var delay = next01 - now; if (delay <= TimeSpan.Zero) delay = TimeSpan.FromHours(24); _logger.LogInformation("OEENightlyJob sleeping until {NextRun}", next01); await Task.Delay(delay, stoppingToken).ConfigureAwait(false); } } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { // Expected: Task.Delay throws when the host cancels stoppingToken during shutdown. _logger.LogInformation("OEENightlyJob stopped due to host shutdown."); } } private async Task RunOEEForAllTenantsAsync(CancellationToken ct) { _logger.LogInformation("OEENightlyJob starting at {UtcNow}", DateTime.UtcNow); using var scope = _scopeFactory.CreateScope(); var engine = scope.ServiceProvider.GetRequiredService(); // TODO: iterate all active tenants via ITenantConnectionProvider // For each tenant, obtain LoginDTO and call: // var toDate = DateTime.UtcNow.Date; // var fromDate = toDate.AddDays(-1); // await engine.CalculateAndPersistOEE(fromDate, toDate, tenantLogin, ct); // // Until tenant iteration is wired: no-op to prevent startup errors. await Task.CompletedTask.ConfigureAwait(false); _logger.LogInformation("OEENightlyJob complete at {UtcNow}", DateTime.UtcNow); } } }