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:
@@ -0,0 +1,462 @@
|
||||
# Guida Utente - Schedulazione a Intervalli
|
||||
|
||||
## 🎯 **Come Creare una Schedulazione a Intervalli**
|
||||
|
||||
### Opzione 1: Tramite UI (Quando Implementata)
|
||||
|
||||
1. **Naviga alla sezione Schedulazioni**
|
||||
- Menu → Schedulazioni
|
||||
|
||||
2. **Clicca "Nuova Schedulazione"**
|
||||
|
||||
3. **Compila il Form**
|
||||
```
|
||||
Nome: [Nome descrittivo]
|
||||
Profilo: [Seleziona profilo esistente]
|
||||
Tipo: [Seleziona "A Intervalli"]
|
||||
|
||||
→ Appare configurazione intervalli:
|
||||
|
||||
Valore Intervallo: [Numero] (es. 5, 10, 30)
|
||||
Unità: [Seleziona]
|
||||
- Secondi
|
||||
- Minuti ← Raccomandato
|
||||
- Ore
|
||||
- Giorni
|
||||
- Settimane
|
||||
- Mesi
|
||||
```
|
||||
|
||||
4. **Salva e Attiva**
|
||||
- ✅ Abilita Schedulazione
|
||||
- Salva
|
||||
|
||||
---
|
||||
|
||||
### Opzione 2: Tramite Database Diretto
|
||||
|
||||
```sql
|
||||
-- Inserisci nuova schedulazione a intervalli
|
||||
INSERT INTO ProfileSchedules
|
||||
(
|
||||
Name,
|
||||
Description,
|
||||
ProfileId,
|
||||
IsEnabled,
|
||||
ScheduleType,
|
||||
IntervalValue,
|
||||
IntervalUnit,
|
||||
IsActive,
|
||||
CreatedAt,
|
||||
CreatedBy
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'Sync Clienti Ogni 5 Minuti', -- Nome
|
||||
'Sincronizzazione automatica clienti', -- Descrizione
|
||||
1, -- ID del profilo esistente
|
||||
1, -- Abilitata (1 = true)
|
||||
'interval', -- Tipo schedulazione
|
||||
5, -- Valore intervallo
|
||||
'minutes', -- Unità intervallo
|
||||
1, -- Attiva (1 = true)
|
||||
datetime('now'), -- Data creazione
|
||||
'Admin' -- Creato da
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Opzione 3: Tramite API/Service (C#)
|
||||
|
||||
```csharp
|
||||
using CredentialManager.Services;
|
||||
using CredentialManager.Models;
|
||||
|
||||
// Inject IProfileScheduleService
|
||||
var schedule = new ProfileSchedule
|
||||
{
|
||||
Name = "Sync Prodotti Ogni 10 Minuti",
|
||||
Description = "Sincronizzazione automatica prodotti",
|
||||
ProfileId = 2, // ID profilo esistente
|
||||
ScheduleType = "interval",
|
||||
IntervalValue = 10,
|
||||
IntervalUnit = "minutes",
|
||||
IsEnabled = true,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
var created = await scheduleService.CreateScheduleAsync(schedule);
|
||||
Console.WriteLine($"Schedulazione creata con ID: {created.Id}");
|
||||
Console.WriteLine($"Prossima esecuzione: {created.NextExecutionTime}");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 **Esempi di Configurazione Comuni**
|
||||
|
||||
### 🔵 Sincronizzazione Frequente (Ogni 5 Minuti)
|
||||
|
||||
**Caso d'uso**: Dati che cambiano frequentemente (ordini, inventario real-time)
|
||||
|
||||
```sql
|
||||
IntervalValue: 5
|
||||
IntervalUnit: minutes
|
||||
```
|
||||
|
||||
**Esecuzione**: 10:00, 10:05, 10:10, 10:15, 10:20, ...
|
||||
|
||||
---
|
||||
|
||||
### 🟢 Sincronizzazione Oraria (Ogni 1 Ora)
|
||||
|
||||
**Caso d'uso**: Report, statistiche, aggregazioni
|
||||
|
||||
```sql
|
||||
IntervalValue: 1
|
||||
IntervalUnit: hours
|
||||
```
|
||||
|
||||
**Esecuzione**: 08:00, 09:00, 10:00, 11:00, 12:00, ...
|
||||
|
||||
---
|
||||
|
||||
### 🟡 Backup Semi-Orario (Ogni 30 Minuti)
|
||||
|
||||
**Caso d'uso**: Backup incrementali, snapshot dati
|
||||
|
||||
```sql
|
||||
IntervalValue: 30
|
||||
IntervalUnit: minutes
|
||||
```
|
||||
|
||||
**Esecuzione**: 08:00, 08:30, 09:00, 09:30, 10:00, ...
|
||||
|
||||
---
|
||||
|
||||
### 🔴 Test Rapido (Ogni 30 Secondi)
|
||||
|
||||
**Caso d'uso**: Test, debugging, demo
|
||||
|
||||
```sql
|
||||
IntervalValue: 30
|
||||
IntervalUnit: seconds
|
||||
```
|
||||
|
||||
**Esecuzione**: 14:30:00, 14:30:30, 14:31:00, 14:31:30, ...
|
||||
|
||||
⚠️ **Solo per ambienti di test!**
|
||||
|
||||
---
|
||||
|
||||
### 🟣 Sincronizzazione Giornaliera (Ogni 2 Giorni)
|
||||
|
||||
**Caso d'uso**: Archivi, dati storici, backup completi
|
||||
|
||||
```sql
|
||||
IntervalValue: 2
|
||||
IntervalUnit: days
|
||||
```
|
||||
|
||||
**Esecuzione**: 01/10 00:00, 03/10 00:00, 05/10 00:00, ...
|
||||
|
||||
---
|
||||
|
||||
### 🟠 Report Settimanale (Ogni 1 Settimana)
|
||||
|
||||
**Caso d'uso**: Report settimanali, consolidamenti
|
||||
|
||||
```sql
|
||||
IntervalValue: 1
|
||||
IntervalUnit: weeks
|
||||
```
|
||||
|
||||
**Esecuzione**: 01/10, 08/10, 15/10, 22/10, 29/10, ...
|
||||
|
||||
---
|
||||
|
||||
### ⚫ Archivio Mensile (Ogni 1 Mese)
|
||||
|
||||
**Caso d'uso**: Archiviazioni mensili, report fiscali
|
||||
|
||||
```sql
|
||||
IntervalValue: 1
|
||||
IntervalUnit: months
|
||||
```
|
||||
|
||||
**Esecuzione**: 01/10, 01/11, 01/12, 01/01, ...
|
||||
|
||||
---
|
||||
|
||||
## 🔍 **Come Verificare una Schedulazione**
|
||||
|
||||
### Query di Controllo
|
||||
|
||||
```sql
|
||||
-- Verifica schedulazione creata
|
||||
SELECT
|
||||
Id,
|
||||
Name,
|
||||
ScheduleType,
|
||||
IntervalValue,
|
||||
IntervalUnit,
|
||||
CONCAT(IntervalValue, ' ', IntervalUnit) as Interval,
|
||||
IsEnabled,
|
||||
IsActive,
|
||||
LastExecutionTime,
|
||||
NextExecutionTime,
|
||||
LastExecutionStatus
|
||||
FROM ProfileSchedules
|
||||
WHERE ScheduleType = 'interval'
|
||||
ORDER BY Id DESC;
|
||||
```
|
||||
|
||||
### Verifica Prossima Esecuzione
|
||||
|
||||
```sql
|
||||
-- Schedulazioni che verranno eseguite nei prossimi 10 minuti
|
||||
SELECT
|
||||
Id,
|
||||
Name,
|
||||
NextExecutionTime,
|
||||
ROUND((JULIANDAY(NextExecutionTime) - JULIANDAY('now')) * 24 * 60, 1) as MinutesToNext
|
||||
FROM ProfileSchedules
|
||||
WHERE IsEnabled = 1
|
||||
AND IsActive = 1
|
||||
AND NextExecutionTime <= datetime('now', '+10 minutes')
|
||||
ORDER BY NextExecutionTime;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 **Monitoraggio Esecuzioni**
|
||||
|
||||
### Storico Ultime Esecuzioni
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
s.Name as ScheduleName,
|
||||
h.StartTime,
|
||||
h.EndTime,
|
||||
ROUND((JULIANDAY(h.EndTime) - JULIANDAY(h.StartTime)) * 24 * 60, 2) as DurationMinutes,
|
||||
h.Status,
|
||||
h.RecordsProcessed,
|
||||
h.Message
|
||||
FROM ScheduleExecutionHistory h
|
||||
JOIN ProfileSchedules s ON h.ScheduleId = s.Id
|
||||
WHERE s.ScheduleType = 'interval'
|
||||
ORDER BY h.StartTime DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
### Statistiche per Schedulazione
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
s.Name,
|
||||
s.ScheduleType,
|
||||
CONCAT(s.IntervalValue, ' ', s.IntervalUnit) as Interval,
|
||||
COUNT(h.Id) as TotalExecutions,
|
||||
SUM(CASE WHEN h.Status = 'success' THEN 1 ELSE 0 END) as Successes,
|
||||
SUM(CASE WHEN h.Status = 'failed' THEN 1 ELSE 0 END) as Failures,
|
||||
AVG(ROUND((JULIANDAY(h.EndTime) - JULIANDAY(h.StartTime)) * 24 * 60, 2)) as AvgDurationMinutes,
|
||||
SUM(h.RecordsProcessed) as TotalRecords
|
||||
FROM ProfileSchedules s
|
||||
LEFT JOIN ScheduleExecutionHistory h ON s.Id = h.ScheduleId
|
||||
WHERE s.ScheduleType = 'interval'
|
||||
AND h.StartTime >= datetime('now', '-24 hours')
|
||||
GROUP BY s.Id, s.Name, s.ScheduleType, s.IntervalValue, s.IntervalUnit
|
||||
ORDER BY TotalExecutions DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ **Gestione Schedulazioni**
|
||||
|
||||
### Disabilitare Temporaneamente
|
||||
|
||||
```sql
|
||||
-- Disabilita schedulazione (mantiene configurazione)
|
||||
UPDATE ProfileSchedules
|
||||
SET IsEnabled = 0
|
||||
WHERE Id = 1;
|
||||
```
|
||||
|
||||
### Modificare Intervallo
|
||||
|
||||
```sql
|
||||
-- Cambia da 5 minuti a 10 minuti
|
||||
UPDATE ProfileSchedules
|
||||
SET IntervalValue = 10,
|
||||
UpdatedAt = datetime('now')
|
||||
WHERE Id = 1;
|
||||
```
|
||||
|
||||
### Cambiare Unità di Tempo
|
||||
|
||||
```sql
|
||||
-- Cambia da minuti a ore
|
||||
UPDATE ProfileSchedules
|
||||
SET IntervalValue = 1,
|
||||
IntervalUnit = 'hours',
|
||||
UpdatedAt = datetime('now')
|
||||
WHERE Id = 1;
|
||||
```
|
||||
|
||||
### Riattivare Schedulazione
|
||||
|
||||
```sql
|
||||
-- Riattiva schedulazione disabilitata
|
||||
UPDATE ProfileSchedules
|
||||
SET IsEnabled = 1,
|
||||
NextExecutionTime = datetime('now'), -- Esegue subito
|
||||
UpdatedAt = datetime('now')
|
||||
WHERE Id = 1;
|
||||
```
|
||||
|
||||
### Eliminare Schedulazione
|
||||
|
||||
```sql
|
||||
-- Disattiva definitivamente (soft delete)
|
||||
UPDATE ProfileSchedules
|
||||
SET IsActive = 0,
|
||||
IsEnabled = 0,
|
||||
UpdatedAt = datetime('now')
|
||||
WHERE Id = 1;
|
||||
|
||||
-- Oppure elimina fisicamente
|
||||
DELETE FROM ProfileSchedules WHERE Id = 1;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ **Troubleshooting**
|
||||
|
||||
### Schedulazione Non Eseguita
|
||||
|
||||
**Possibili Cause**:
|
||||
|
||||
1. **Schedulazione disabilitata**
|
||||
```sql
|
||||
SELECT IsEnabled, IsActive FROM ProfileSchedules WHERE Id = X;
|
||||
```
|
||||
Soluzione: `UPDATE ProfileSchedules SET IsEnabled = 1 WHERE Id = X;`
|
||||
|
||||
2. **NextExecutionTime nel futuro**
|
||||
```sql
|
||||
SELECT NextExecutionTime, datetime('now') as Now
|
||||
FROM ProfileSchedules WHERE Id = X;
|
||||
```
|
||||
Soluzione: Attendi orario programmato o aggiorna manualmente
|
||||
|
||||
3. **Background service non avviato**
|
||||
- Verifica logs applicazione
|
||||
- Controlla che `ScheduledJobService` sia running
|
||||
|
||||
4. **Profilo non valido**
|
||||
```sql
|
||||
SELECT p.* FROM ProfileSchedules s
|
||||
LEFT JOIN DataCouplerProfiles p ON s.ProfileId = p.Id
|
||||
WHERE s.Id = X;
|
||||
```
|
||||
Soluzione: Verifica che il profilo esista e sia configurato
|
||||
|
||||
---
|
||||
|
||||
### Schedulazione Eseguita Troppo Frequentemente
|
||||
|
||||
**Causa**: Intervallo troppo breve per la durata dell'esecuzione
|
||||
|
||||
**Soluzione**:
|
||||
1. Aumenta intervallo
|
||||
2. Ottimizza profilo (meno dati, filtri migliori)
|
||||
3. Monitora durata:
|
||||
```sql
|
||||
SELECT
|
||||
ROUND((JULIANDAY(EndTime) - JULIANDAY(StartTime)) * 24 * 60, 2) as Minutes
|
||||
FROM ScheduleExecutionHistory
|
||||
WHERE ScheduleId = X
|
||||
ORDER BY StartTime DESC LIMIT 10;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Schedulazione Bloccata (Running >1 Ora)
|
||||
|
||||
**Verifica**:
|
||||
```sql
|
||||
SELECT
|
||||
Id, Name, LastExecutionStatus, LastExecutionTime,
|
||||
ROUND((JULIANDAY('now') - JULIANDAY(LastExecutionTime)) * 24, 1) as HoursSince
|
||||
FROM ProfileSchedules
|
||||
WHERE LastExecutionStatus = 'running'
|
||||
AND LastExecutionTime < datetime('now', '-1 hour');
|
||||
```
|
||||
|
||||
**Soluzione**:
|
||||
```sql
|
||||
-- Reset manuale status
|
||||
UPDATE ProfileSchedules
|
||||
SET LastExecutionStatus = 'failed',
|
||||
LastExecutionMessage = 'Timeout manuale - esecuzione bloccata',
|
||||
NextExecutionTime = datetime('now', '+5 minutes')
|
||||
WHERE Id = X;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 **Best Practices**
|
||||
|
||||
### ✅ Raccomandazioni
|
||||
|
||||
1. **Intervalli Minimi Produzione**: 5-10 minuti
|
||||
2. **Test prima di produzione**: Prova con intervalli brevi in staging
|
||||
3. **Monitora durata**: Assicurati che esecuzione < intervallo
|
||||
4. **Usa descrizioni chiare**: Facilita troubleshooting
|
||||
5. **Backup prima di modifiche**: Salva configurazioni funzionanti
|
||||
|
||||
### ❌ Da Evitare
|
||||
|
||||
1. **Intervalli <1 minuto in produzione** (carico eccessivo)
|
||||
2. **Troppi intervalli brevi contemporanei** (saturazione risorse)
|
||||
3. **Modificare schedulazioni running** (rischio inconsistenza)
|
||||
4. **Eliminare senza disabilitare prima** (perdita log esecuzioni)
|
||||
|
||||
---
|
||||
|
||||
## 🆘 **Supporto**
|
||||
|
||||
### Logs Applicazione
|
||||
|
||||
**Windows Event Log**:
|
||||
```powershell
|
||||
Get-EventLog -LogName Application -Source "ScheduledJobService" -Newest 50
|
||||
```
|
||||
|
||||
**File Log** (se configurato):
|
||||
```powershell
|
||||
Get-Content logs/datacoupler.log -Tail 100
|
||||
```
|
||||
|
||||
### Query Debug
|
||||
|
||||
```sql
|
||||
-- Overview completa schedulazione
|
||||
SELECT
|
||||
s.*,
|
||||
p.Name as ProfileName,
|
||||
p.SourceType,
|
||||
p.DestinationType,
|
||||
(SELECT COUNT(*) FROM ScheduleExecutionHistory WHERE ScheduleId = s.Id) as TotalExecutions,
|
||||
(SELECT COUNT(*) FROM ScheduleExecutionHistory WHERE ScheduleId = s.Id AND Status = 'success') as SuccessCount,
|
||||
(SELECT MAX(StartTime) FROM ScheduleExecutionHistory WHERE ScheduleId = s.Id) as LastActualExecution
|
||||
FROM ProfileSchedules s
|
||||
LEFT JOIN DataCouplerProfiles p ON s.ProfileId = p.Id
|
||||
WHERE s.Id = X;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Versione**: 2.0
|
||||
**Ultimo Aggiornamento**: 2 Ottobre 2025
|
||||
**Documentazione Tecnica**: ADVANCED_SCHEDULING_SYSTEM.md
|
||||
Reference in New Issue
Block a user