using AutomationBLL.Calendar; using AutomationBLL.Run; using AutomationBLL.ScriptVersion; using AutomationBLL.Target; using GB5Shared.DTO.Framework.Login; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; namespace AutomationBLL.Schedule; // The "internal dispatch endpoint" a scheduled JobEngine job calls into (via the // automation.scheduled.dispatch Dapr topic — see AutomationSL/Subscriptions/ScheduledDispatchSubscriber.cs). // Applies the holiday-skip and dependency-chain checks that neither GB5 scheduler has built in, // then enqueues a run through the same path TriggerRun uses. public class ScheduledDispatchBLL : IScheduledDispatchBLL { private readonly ITargetBLL _targetBll; private readonly IScheduleBLL _scheduleBll; private readonly ICalendarCheckBLL _calendarBll; private readonly IScriptVersionBLL _versionBll; private readonly IRunBLL _runBll; private readonly ILogger _logger; public ScheduledDispatchBLL( ITargetBLL targetBll, IScheduleBLL scheduleBll, ICalendarCheckBLL calendarBll, IScriptVersionBLL versionBll, IRunBLL runBll, ILogger logger) { _targetBll = targetBll; _scheduleBll = scheduleBll; _calendarBll = calendarBll; _versionBll = versionBll; _runBll = runBll; _logger = logger; } public async Task DispatchScheduledRun(int targetId, LoginDTO login, CancellationToken ct) { GB5Trace.Step("dispatch-scheduled-run", new { targetId }); var target = await _targetBll.GetById(targetId, login, ct).ConfigureAwait(false) ?? throw new InvalidOperationException($"Target {targetId} not found."); if (!target.IsEnabled) { _logger.LogInformation("Scheduled dispatch skipped for TargetId {TargetId}: target is disabled", targetId); return "Skipped: target is disabled"; } var schedule = await _scheduleBll.GetByTarget(targetId, login, ct).ConfigureAwait(false) ?? throw new InvalidOperationException($"No schedule found for Target {targetId}."); if (schedule.SkipHolidays && await _calendarBll.IsHolidayAsync(target.CalendarId, DateTime.UtcNow, login, ct).ConfigureAwait(false)) { _logger.LogInformation("Scheduled dispatch skipped for TargetId {TargetId}: today is a holiday on CalendarId {CalendarId}", targetId, target.CalendarId); return "Skipped: holiday"; } if (schedule.DependsOnScriptId.HasValue) { var depStatus = await _runBll.GetLatestStatusForScript(schedule.DependsOnScriptId.Value, login, ct).ConfigureAwait(false); if (depStatus != "Success") { _logger.LogInformation( "Scheduled dispatch skipped for TargetId {TargetId}: dependency ScriptId {DependsOnScriptId} last status is {Status}", targetId, schedule.DependsOnScriptId, depStatus ?? "(never run)"); return $"Skipped: dependency not satisfied (last status: {depStatus ?? "never run"})"; } } var version = await _versionBll.GetLatestReleased(target.ScriptId, login, ct).ConfigureAwait(false) ?? throw new InvalidOperationException($"No released version found for ScriptId {target.ScriptId} — cannot dispatch."); return await _runBll.EnqueueRun(target.ScriptId, version.VersionId, "Schedule", null, targetId, login, ct).ConfigureAwait(false); } }