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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user