using Microsoft.Extensions.Options; using SeaHaven.Services.Helpers; using SeaHaven.Services.Implementation; using SeaHaven.Services.Interfaces; using Api.SeaHavenIndustries.Options; using Api.SeaHavenIndustries.Observability; using Sentry; namespace Api.SeaHavenIndustries.HostedServices { public class WorkOrderWeekRolledHostedService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private readonly WeekRolledJobOptions _options; private readonly WorkOrderJobRunState _runState; private readonly IHub _sentryHub; private DateOnly? _lastRunDate; public WorkOrderWeekRolledHostedService( IServiceScopeFactory scopeFactory, IOptions options, WorkOrderJobRunState runState, ILogger logger, IHub sentryHub) { _scopeFactory = scopeFactory; _logger = logger; _options = options.Value.WeekRolled; _runState = runState; _sentryHub = sentryHub; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { if (_options.Enabled && ShouldRunNow()) { var today = DateOnly.FromDateTime(DateTime.UtcNow); if (_lastRunDate != today) { if (await RunJobAsync(stoppingToken)) _lastRunDate = today; } } await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } } private async Task RunJobAsync(CancellationToken stoppingToken) { using var transaction = SentryObservability.BeginBackgroundTransaction( _sentryHub, "workorders.week-rolled-job", $"{nameof(WorkOrderWeekRolledHostedService)}.{nameof(RunJobAsync)}"); try { var sourceWeekStart = WorkOrderOperationalWeek.GetPreviousOperationalWeekStart(DateTime.UtcNow); using var scope = _scopeFactory.CreateScope(); var service = scope.ServiceProvider.GetRequiredService(); await service.ProcessWeekRolledAsync(sourceWeekStart, stoppingToken); _runState.LastWeekRolledRunUtc = DateTime.UtcNow; _runState.LastWeekRolledError = null; transaction.FinishOk(); return true; } catch (OperationCanceledException) { transaction.FinishCancelled(); throw; } catch (Exception ex) when (ex is not OperationCanceledException) { _runState.LastWeekRolledError = ex.Message; _logger.LogError(ex, "WeekRolled hosted job failed."); transaction.FinishError(ex); return false; } } private bool ShouldRunNow() { var now = DateTime.UtcNow; if (!Enum.TryParse(_options.DayOfWeek, ignoreCase: true, out var targetDay)) targetDay = DayOfWeek.Monday; if (now.DayOfWeek != targetDay) return false; if (!TimeOnly.TryParse(_options.RunAtUtc, out var runAt)) runAt = new TimeOnly(0, 5); return TimeOnly.FromDateTime(now) >= runAt; } } }