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
+14 -24
View File
@@ -1812,36 +1812,25 @@ public partial class DataCoupler : ComponentBase
}
/// <summary>
/// Genera un hash SHA256 dei dati dei campi sorgente mappati.
/// Genera un hash SHA256 dei dati del record passato come parametro.
/// Utilizzato per rilevare cambiamenti nei dati e ottimizzare il trasferimento.
/// Include anche una signature dei campi mappati per rilevare cambi di configurazione.
/// Calcola l'hash SOLO sui campi presenti nel record, in ordine alfabetico.
/// </summary>
private string GenerateDataHash(Dictionary<string, object> record)
{
try
{
// Raccoglie i valori dei campi mappati in ordine alfabetico per garantire consistenza
var mappedFields = fieldMappings.Keys.OrderBy(k => k).ToList();
var valuesForHash = new List<string>();
// PRIMO: Aggiungi la signature dei mapping per rilevare cambi di configurazione
var mappingSignature = string.Join(",", fieldMappings.OrderBy(m => m.Key).Select(m => $"{m.Key}->{m.Value}"));
valuesForHash.Add($"MAPPING_SIGNATURE={mappingSignature}");
// Ordina le chiavi alfabeticamente per garantire consistenza
var orderedKeys = record.Keys.OrderBy(k => k).ToList();
// SECONDO: Aggiungi i valori dei dati per ogni campo mappato
foreach (var sourceField in mappedFields)
// Aggiungi i valori dei dati per ogni campo presente nel record
foreach (var key in orderedKeys)
{
if (record.ContainsKey(sourceField))
{
var value = record[sourceField];
var normalizedValue = value?.ToString()?.Trim() ?? "";
valuesForHash.Add($"{sourceField}={normalizedValue}");
}
else
{
// Se il campo non è presente nel record, aggiungi una stringa vuota
valuesForHash.Add($"{sourceField}=");
}
var value = record[key];
var normalizedValue = value?.ToString()?.Trim() ?? "";
valuesForHash.Add($"{key}={normalizedValue}");
}
// Combina tutti i valori in una stringa unica
@@ -1855,7 +1844,7 @@ public partial class DataCoupler : ComponentBase
var hashBytes = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(combinedData));
var hashString = Convert.ToHexString(hashBytes);
Logger.LogDebug("Hash SHA256 generato: {Hash} (include signature mapping)", hashString);
Logger.LogDebug("Hash SHA256 generato: {Hash} per {FieldCount} campi", hashString, orderedKeys.Count);
return hashString;
}
}
@@ -2578,7 +2567,8 @@ public partial class DataCoupler : ComponentBase
// Genera la chiave sorgente e l'hash dei dati per questo record (operazioni locali, thread-safe)
var sourceKey = GenerateSourceKey(record);
var currentDataHash = GenerateDataHash(record);
// ✅ Calcola l'hash SOLO sui dati trasformati/mappati che vengono effettivamente trasferiti
var currentDataHash = GenerateDataHash(restData);
// Analizza le associazioni per capire se aggiornare, creare o saltare
if (currentUseRecordAssociations && !string.IsNullOrEmpty(sourceKey))
@@ -2738,8 +2728,8 @@ public partial class DataCoupler : ComponentBase
if (useRecordAssociations && !string.IsNullOrEmpty(transferResult.EntityId))
{
// IMPORTANTE: Non awaita qui, solo crea il task per esecuzione parallela
// Genera l'hash per questo record per salvarlo nell'associazione
var dataHashForAssociation = GenerateDataHash(originalData.originalRecord);
// Genera l'hash SOLO sui dati trasformati/mappati che sono stati effettivamente trasferiti
var dataHashForAssociation = GenerateDataHash(originalData.transformedData);
var associationTask = CreateAssociationAsync(originalData.originalRecord, transferResult.EntityId, originalData.recordNumber, dataHashForAssociation);
createAssociationTasks.Add(associationTask);
}