155 lines
5.3 KiB
C#
155 lines
5.3 KiB
C#
|
|
using Microsoft.EntityFrameworkCore;
|
||
|
|
using GestorFacturas.API.Data;
|
||
|
|
using GestorFacturas.API.Models;
|
||
|
|
using GestorFacturas.API.Services.Interfaces;
|
||
|
|
|
||
|
|
namespace GestorFacturas.API.Workers;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Worker Service que ejecuta el procesamiento de facturas de forma programada
|
||
|
|
/// </summary>
|
||
|
|
public class CronogramaWorker : BackgroundService
|
||
|
|
{
|
||
|
|
private readonly IServiceProvider _serviceProvider;
|
||
|
|
private readonly ILogger<CronogramaWorker> _logger;
|
||
|
|
// Eliminamos el Timer antiguo
|
||
|
|
|
||
|
|
public CronogramaWorker(
|
||
|
|
IServiceProvider serviceProvider,
|
||
|
|
ILogger<CronogramaWorker> logger)
|
||
|
|
{
|
||
|
|
_serviceProvider = serviceProvider;
|
||
|
|
_logger = logger;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||
|
|
{
|
||
|
|
_logger.LogInformation("CronogramaWorker iniciado correctamente.");
|
||
|
|
|
||
|
|
// PeriodicTimer espera a que termine la ejecución antes de contar el siguiente intervalo.
|
||
|
|
// Iniciamos con un tick de 1 minuto para chequear.
|
||
|
|
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
// Bucle infinito mientras el servicio esté activo
|
||
|
|
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||
|
|
{
|
||
|
|
await VerificarYEjecutar(stoppingToken);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException)
|
||
|
|
{
|
||
|
|
_logger.LogInformation("CronogramaWorker detenido.");
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.LogError(ex, "Error fatal en el ciclo del CronogramaWorker");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task VerificarYEjecutar(CancellationToken stoppingToken)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
using var scope = _serviceProvider.CreateScope();
|
||
|
|
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||
|
|
|
||
|
|
var config = await context.Configuraciones.FirstOrDefaultAsync(c => c.Id == 1, stoppingToken);
|
||
|
|
|
||
|
|
if (config == null)
|
||
|
|
{
|
||
|
|
_logger.LogWarning("No se encontró configuración del sistema");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verificar si el servicio está activo
|
||
|
|
if (!config.EnEjecucion)
|
||
|
|
{
|
||
|
|
// Solo loguear en nivel Debug para no saturar los logs
|
||
|
|
_logger.LogDebug("Servicio en estado detenido");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Determinar si toca ejecutar según la periodicidad
|
||
|
|
if (!DebeEjecutar(config))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_logger.LogInformation("¡Es momento de ejecutar el proceso de facturas!");
|
||
|
|
|
||
|
|
// Ejecutar el proceso
|
||
|
|
var procesador = scope.ServiceProvider.GetRequiredService<IProcesadorFacturasService>();
|
||
|
|
|
||
|
|
// Calcular fecha desde basándonos en la última ejecución o periodicidad
|
||
|
|
DateTime fechaDesde = CalcularFechaDesde(config);
|
||
|
|
|
||
|
|
await procesador.EjecutarProcesoAsync(fechaDesde);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.LogError(ex, "Error en CronogramaWorker al verificar y ejecutar");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool DebeEjecutar(Configuracion config)
|
||
|
|
{
|
||
|
|
if (config.UltimaEjecucion == null) return true;
|
||
|
|
|
||
|
|
var ahora = DateTime.Now;
|
||
|
|
var ultimaEjecucion = config.UltimaEjecucion.Value;
|
||
|
|
|
||
|
|
if (!TimeSpan.TryParse(config.HoraEjecucion, out TimeSpan horaConfigurada))
|
||
|
|
{
|
||
|
|
horaConfigurada = TimeSpan.Zero;
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (config.Periodicidad.ToUpper())
|
||
|
|
{
|
||
|
|
case "MINUTOS":
|
||
|
|
var minutosTranscurridos = (ahora - ultimaEjecucion).TotalMinutes;
|
||
|
|
return minutosTranscurridos >= config.ValorPeriodicidad;
|
||
|
|
|
||
|
|
case "DIAS":
|
||
|
|
case "DÍAS":
|
||
|
|
var diasTranscurridos = (ahora.Date - ultimaEjecucion.Date).Days;
|
||
|
|
if (diasTranscurridos < config.ValorPeriodicidad) return false;
|
||
|
|
|
||
|
|
var horaActual = ahora.TimeOfDay;
|
||
|
|
var yaEjecutadoHoy = ultimaEjecucion.Date == ahora.Date;
|
||
|
|
|
||
|
|
// Ejecuta si pasó la hora configurada Y no se ha ejecutado hoy
|
||
|
|
return horaActual >= horaConfigurada && !yaEjecutadoHoy;
|
||
|
|
|
||
|
|
case "MESES":
|
||
|
|
var mesesTranscurridos = ((ahora.Year - ultimaEjecucion.Year) * 12) + ahora.Month - ultimaEjecucion.Month;
|
||
|
|
if (mesesTranscurridos < config.ValorPeriodicidad) return false;
|
||
|
|
|
||
|
|
return ahora.TimeOfDay >= horaConfigurada && !(ultimaEjecucion.Date == ahora.Date);
|
||
|
|
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private DateTime CalcularFechaDesde(Configuracion config)
|
||
|
|
{
|
||
|
|
// Buffer de seguridad de 10 días
|
||
|
|
int diasBuffer = 10;
|
||
|
|
|
||
|
|
if (config.UltimaEjecucion != null)
|
||
|
|
{
|
||
|
|
return config.UltimaEjecucion.Value.Date.AddDays(-diasBuffer);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Si es la primera vez, usa la periodicidad + buffer
|
||
|
|
return config.Periodicidad.ToUpper() switch
|
||
|
|
{
|
||
|
|
"MINUTOS" => DateTime.Now.AddMinutes(-config.ValorPeriodicidad).AddDays(-diasBuffer),
|
||
|
|
"DIAS" or "DÍAS" => DateTime.Now.AddDays(-config.ValorPeriodicidad - diasBuffer),
|
||
|
|
"MESES" => DateTime.Now.AddMonths(-config.ValorPeriodicidad).AddDays(-diasBuffer),
|
||
|
|
_ => DateTime.Today.AddDays(-diasBuffer)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|