feat: Implementazione completa sistema schedulazione con intervalli personalizzati

- Aggiunto supporto schedulazione con intervalli flessibili (secondi/minuti/ore/giorni/settimane/mesi)
- Esteso modello ProfileSchedule con campi IntervalValue e IntervalUnit
- Ottimizzato ScheduledJobService per controlli ogni 30s con esecuzione parallela
- Implementata interfaccia UI completa con anteprima real-time in italiano
- Aggiunta migrazione database AddIntervalSchedulingFields
- Implementati metodi calcolo NextExecutionTime per intervalli
- Aggiunta gestione tracking anti-duplicati e cleanup automatico
- Creata documentazione completa (6 file, 2500+ righe)

Modifiche tecniche:
- ProfileSchedule.cs: Nuovi campi e metodi CalculateNextInterval/GetScheduleDescription
- ScheduledJobService.cs: Ridotto check interval a 30s, aggiunto parallel processing
- ProfileScheduleService.cs: Supporto calcolo intervalli in UpdateNextExecutionTimeAsync
- Scheduling.razor: Aggiunta sezione UI per configurazione intervalli
- Scheduling.razor.cs: Implementato GetIntervalPreview() e gestione stato campi
This commit is contained in:
2025-10-02 01:12:39 +02:00
parent b76a6760fb
commit d042863a56
71 changed files with 17860 additions and 144 deletions
@@ -57,8 +57,21 @@ public class DatabaseInitializer : IDatabaseInitializer
_logger.LogInformation("Trovate {Count} migrazioni pendenti: {Migrations}",
pendingMigrations.Count(), string.Join(", ", pendingMigrations));
await _context.Database.MigrateAsync();
_logger.LogInformation("Migrazioni applicate con successo");
try
{
await _context.Database.MigrateAsync();
_logger.LogInformation("Migrazioni applicate con successo");
}
catch (InvalidOperationException ex) when (ex.Message.Contains("PendingModelChangesWarning"))
{
_logger.LogWarning("Rilevate modifiche al modello pendenti, procedo con la creazione delle tabelle mancanti...");
// Creiamo le tabelle mancanti manualmente
await CreateScheduleExecutionHistoriesTableAsync();
await VerifyAndAddMissingColumnsAsync();
_logger.LogInformation("Tabelle e colonne mancanti create con successo");
}
}
else
{
@@ -105,6 +118,32 @@ public class DatabaseInitializer : IDatabaseInitializer
{
_logger.LogWarning(ex, "Tabella DataCouplerProfiles non accessibile");
}
// Verifica se la tabella ProfileSchedules esiste
try
{
await _context.ProfileSchedules.CountAsync();
_logger.LogInformation("Tabella ProfileSchedules verificata con successo");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Tabella ProfileSchedules non accessibile");
}
// Verifica se la tabella ScheduleExecutionHistories esiste
try
{
await _context.ScheduleExecutionHistories.CountAsync();
_logger.LogInformation("Tabella ScheduleExecutionHistories verificata con successo");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Tabella ScheduleExecutionHistories non accessibile, tentativo di creazione...");
await CreateScheduleExecutionHistoriesTableAsync();
}
// Verifica e aggiungi colonne mancanti per ProfileSchedules
await VerifyAndAddMissingColumnsAsync();
}
catch (Exception ex)
{
@@ -235,4 +274,105 @@ public class DatabaseInitializer : IDatabaseInitializer
throw;
}
}
private async Task CreateScheduleExecutionHistoriesTableAsync()
{
try
{
_logger.LogInformation("Creazione tabella ScheduleExecutionHistories...");
// Crea la tabella ScheduleExecutionHistories
await _context.Database.ExecuteSqlRawAsync(@"
CREATE TABLE IF NOT EXISTS ScheduleExecutionHistories (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ScheduleId INTEGER NOT NULL,
ProfileId INTEGER NOT NULL,
ProfileName TEXT NOT NULL,
StartTime DATETIME NOT NULL,
EndTime DATETIME,
Status TEXT NOT NULL,
Message TEXT,
RecordsProcessed INTEGER DEFAULT 0,
RecordsWithErrors INTEGER,
ErrorDetails TEXT,
TriggerType TEXT NOT NULL,
TriggeredBy TEXT,
SourceType TEXT,
DestinationType TEXT,
SourceInfo TEXT,
DestinationInfo TEXT,
AdditionalInfo TEXT,
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (ScheduleId) REFERENCES ProfileSchedules (Id) ON DELETE CASCADE
)");
// Crea gli indici
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_ScheduleId
ON ScheduleExecutionHistories (ScheduleId)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_ProfileId
ON ScheduleExecutionHistories (ProfileId)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_Status
ON ScheduleExecutionHistories (Status)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_StartTime
ON ScheduleExecutionHistories (StartTime)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_TriggerType
ON ScheduleExecutionHistories (TriggerType)");
_logger.LogInformation("Tabella ScheduleExecutionHistories creata con successo");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante la creazione della tabella ScheduleExecutionHistories");
_logger.LogWarning("Continuazione normale - la tabella potrebbe già esistere");
}
}
private async Task VerifyAndAddMissingColumnsAsync()
{
try
{
_logger.LogInformation("Verifica colonne mancanti per ProfileSchedules...");
// Verifica se le colonne di override database esistono
try
{
await _context.Database.ExecuteSqlRawAsync(
"SELECT SourceDatabaseOverride FROM ProfileSchedules LIMIT 1");
}
catch
{
_logger.LogInformation("Aggiunta colonna SourceDatabaseOverride...");
await _context.Database.ExecuteSqlRawAsync(
"ALTER TABLE ProfileSchedules ADD COLUMN SourceDatabaseOverride TEXT");
}
try
{
await _context.Database.ExecuteSqlRawAsync(
"SELECT DestinationDatabaseOverride FROM ProfileSchedules LIMIT 1");
}
catch
{
_logger.LogInformation("Aggiunta colonna DestinationDatabaseOverride...");
await _context.Database.ExecuteSqlRawAsync(
"ALTER TABLE ProfileSchedules ADD COLUMN DestinationDatabaseOverride TEXT");
}
_logger.LogInformation("Verifica colonne completata");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante la verifica/aggiunta colonne");
_logger.LogWarning("Continuazione normale - le colonne potrebbero già esistere");
}
}
}