[Feature] Disabilitata deletion sync nei trasferimenti manuali e aggiunta configurazione nelle schedulazioni
- Disabilitata completamente la sincronizzazione eliminazioni nei trasferimenti manuali (DataCoupler.razor.cs) - Aggiunto campo EnableDeletionSync al modello ProfileSchedule (default: false) - Implementata logica condizionale in ScheduledProfileExecutionService per deletion sync - Aggiunta sezione 'Opzioni Avanzate' nell'interfaccia schedulazione con warning - Creata migration Entity Framework AddEnableDeletionSyncToProfileSchedule - Aggiornato BackupModels per supporto backup/restore del nuovo campo - Aggiornata documentazione README.md e copilot-instructions.md - La deletion sync è ora disponibile solo per schedulazioni con configurazione esplicita per massima sicurezza
This commit is contained in:
@@ -28,6 +28,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
private readonly IDataConnectionCredentialService _dataConnectionCredentialService;
|
||||
private readonly IKeyAssociationService _keyAssociationService;
|
||||
private readonly IAssociationService _associationService;
|
||||
private readonly IDeletionSyncService _deletionSyncService;
|
||||
private readonly ILogger<ScheduledProfileExecutionService> _logger;
|
||||
|
||||
public ScheduledProfileExecutionService(
|
||||
@@ -37,6 +38,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
IDataConnectionCredentialService dataConnectionCredentialService,
|
||||
IKeyAssociationService keyAssociationService,
|
||||
IAssociationService associationService,
|
||||
IDeletionSyncService deletionSyncService,
|
||||
ILogger<ScheduledProfileExecutionService> logger)
|
||||
{
|
||||
_profileService = profileService;
|
||||
@@ -45,6 +47,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
_dataConnectionCredentialService = dataConnectionCredentialService;
|
||||
_keyAssociationService = keyAssociationService;
|
||||
_associationService = associationService;
|
||||
_deletionSyncService = deletionSyncService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -52,6 +55,14 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
/// Esegue un profilo Data Coupler specificato dall'ID
|
||||
/// </summary>
|
||||
public async Task<ProfileExecutionResult> ExecuteProfileAsync(int profileId)
|
||||
{
|
||||
return await ExecuteProfileAsync(profileId, enableDeletionSync: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue un profilo Data Coupler specificato dall'ID con configurazione sincronizzazione eliminazioni
|
||||
/// </summary>
|
||||
public async Task<ProfileExecutionResult> ExecuteProfileAsync(int profileId, bool enableDeletionSync)
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
var result = new ProfileExecutionResult
|
||||
@@ -61,7 +72,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Inizio esecuzione profilo schedulato ID: {ProfileId}", profileId);
|
||||
_logger.LogInformation("Inizio esecuzione profilo schedulato ID: {ProfileId} - DeletionSync: {DeletionSync}", profileId, enableDeletionSync);
|
||||
|
||||
// Carica il profilo
|
||||
var profile = await _profileService.GetProfileByIdAsync(profileId);
|
||||
@@ -78,7 +89,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
await _profileService.UpdateLastUsedAsync(profile.Id);
|
||||
|
||||
// Esegue il trasferimento dati con la logica completa
|
||||
var recordsTransferred = await ExecuteDataTransferAsync(profile);
|
||||
var recordsTransferred = await ExecuteDataTransferAsync(profile, enableDeletionSync);
|
||||
|
||||
result.Success = true;
|
||||
result.RecordsProcessed = recordsTransferred;
|
||||
@@ -106,10 +117,11 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
/// Metodo principale per l'esecuzione del trasferimento dati
|
||||
/// Implementa la stessa logica di StartDataTransferWithComposite
|
||||
/// </summary>
|
||||
private async Task<int> ExecuteDataTransferAsync(DataCouplerProfile profile)
|
||||
private async Task<int> ExecuteDataTransferAsync(DataCouplerProfile profile, bool enableDeletionSync = false)
|
||||
{
|
||||
_logger.LogInformation("=== INIZIO TRASFERIMENTO DATI SCHEDULATO ===");
|
||||
_logger.LogInformation("Esecuzione profilo: {ProfileName} (ID: {ProfileId})", profile.Name, profile.Id);
|
||||
_logger.LogInformation("Esecuzione profilo: {ProfileName} (ID: {ProfileId}) - DeletionSync: {DeletionSync}",
|
||||
profile.Name, profile.Id, enableDeletionSync);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -151,12 +163,12 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
if (useSalesforceComposite)
|
||||
{
|
||||
_logger.LogInformation("Utilizzo Salesforce Composite API per il trasferimento");
|
||||
return await ExecuteDataTransferWithCompositeAsync(profile, sourceRecords, restClient, restEntity, restCredential!, fieldMappings);
|
||||
return await ExecuteDataTransferWithCompositeAsync(profile, sourceRecords, restClient, restEntity, restCredential!, fieldMappings, enableDeletionSync);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Utilizzo metodo trasferimento standard per il trasferimento");
|
||||
return await ExecuteDataTransferStandardAsync(profile, sourceRecords, restClient, restEntity, restCredential!, fieldMappings);
|
||||
return await ExecuteDataTransferStandardAsync(profile, sourceRecords, restClient, restEntity, restCredential!, fieldMappings, enableDeletionSync);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -389,9 +401,11 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
IRestServiceClient restClient,
|
||||
RestEntitySummary restEntity,
|
||||
RestApiCredential restCredential,
|
||||
Dictionary<string, string> fieldMappings)
|
||||
Dictionary<string, string> fieldMappings,
|
||||
bool enableDeletionSync = false)
|
||||
{
|
||||
_logger.LogInformation("Iniziando trasferimento dati standard per {RecordCount} record", sourceRecords.Count());
|
||||
_logger.LogInformation("Iniziando trasferimento dati standard per {RecordCount} record - DeletionSync: {DeletionSync}",
|
||||
sourceRecords.Count(), enableDeletionSync);
|
||||
|
||||
int successCount = 0;
|
||||
int errorCount = 0;
|
||||
@@ -454,6 +468,50 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
_logger.LogInformation("Trasferimento completato. Successi: {SuccessCount}, Errori: {ErrorCount}",
|
||||
successCount, errorCount);
|
||||
|
||||
// Sincronizzazione cancellazioni (se abilitata)
|
||||
if (enableDeletionSync && profile.UseRecordAssociations && !string.IsNullOrEmpty(profile.SourceKeyField))
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("SCHEDULED: Inizio sincronizzazione cancellazioni...");
|
||||
|
||||
// Estrai tutti i valori chiave presenti nella sorgente
|
||||
var sourceKeyValues = sourceRecords
|
||||
.Select(r => r.ContainsKey(profile.SourceKeyField) ? r[profile.SourceKeyField]?.ToString() : null)
|
||||
.Where(k => !string.IsNullOrEmpty(k))
|
||||
.Cast<string>()
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
_logger.LogInformation("SCHEDULED: Trovati {Count} valori chiave nella sorgente", sourceKeyValues.Count);
|
||||
|
||||
// Sincronizza le cancellazioni
|
||||
var deletionOptions = new DeletionSyncOptions
|
||||
{
|
||||
Action = DeletionAction.Delete // Default: elimina fisicamente
|
||||
};
|
||||
|
||||
var deletionResult = await _deletionSyncService.SyncDeletionsAsync(
|
||||
sourceKeyValues,
|
||||
restEntity.Name,
|
||||
restCredential.Name,
|
||||
restClient,
|
||||
deletionOptions);
|
||||
|
||||
if (deletionResult.DeletedRecordsDetected > 0)
|
||||
{
|
||||
_logger.LogInformation("SCHEDULED: Sincronizzazione cancellazioni completata - {Detected} rilevati, {Synced} sincronizzati, {Errors} errori",
|
||||
deletionResult.DeletedRecordsDetected,
|
||||
deletionResult.DeletedRecordsSynced,
|
||||
deletionResult.SyncErrors);
|
||||
}
|
||||
}
|
||||
catch (Exception delEx)
|
||||
{
|
||||
_logger.LogError(delEx, "SCHEDULED: Errore durante la sincronizzazione delle cancellazioni");
|
||||
}
|
||||
}
|
||||
|
||||
return successCount;
|
||||
}
|
||||
|
||||
@@ -467,15 +525,17 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
IRestServiceClient restClient,
|
||||
RestEntitySummary restEntity,
|
||||
RestApiCredential restCredential,
|
||||
Dictionary<string, string> fieldMappings)
|
||||
Dictionary<string, string> fieldMappings,
|
||||
bool enableDeletionSync = false)
|
||||
{
|
||||
_logger.LogInformation("Iniziando trasferimento dati COMPOSITE per {RecordCount} record", sourceRecords.Count());
|
||||
_logger.LogInformation("Iniziando trasferimento dati COMPOSITE per {RecordCount} record - DeletionSync: {DeletionSync}",
|
||||
sourceRecords.Count(), enableDeletionSync);
|
||||
|
||||
// Verifica che sia effettivamente un SalesforceServiceClient
|
||||
if (!(restClient is DataConnection.REST.Implementations.SalesforceServiceClient salesforceClient))
|
||||
{
|
||||
_logger.LogWarning("Client REST non è SalesforceServiceClient, fallback al metodo standard");
|
||||
return await ExecuteDataTransferStandardAsync(profile, sourceRecords, restClient, restEntity, restCredential, fieldMappings);
|
||||
return await ExecuteDataTransferStandardAsync(profile, sourceRecords, restClient, restEntity, restCredential, fieldMappings, enableDeletionSync);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -738,6 +798,50 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
||||
_logger.LogInformation("COMPOSITE SCHEDULED: Trasferimento completato. Creazioni: {SuccessCount}, Aggiornamenti: {UpdatedCount}, Saltati: {SkippedCount}, Errori: {ErrorCount}",
|
||||
successCount, updatedCount, skippedCount, errorCount);
|
||||
|
||||
// Sincronizzazione cancellazioni (se abilitata)
|
||||
if (enableDeletionSync && currentUseRecordAssociations && !string.IsNullOrEmpty(profile.SourceKeyField))
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("COMPOSITE SCHEDULED: Inizio sincronizzazione cancellazioni...");
|
||||
|
||||
// Estrai tutti i valori chiave presenti nella sorgente
|
||||
var sourceKeyValues = sourceRecords
|
||||
.Select(r => r.ContainsKey(profile.SourceKeyField) ? r[profile.SourceKeyField]?.ToString() : null)
|
||||
.Where(k => !string.IsNullOrEmpty(k))
|
||||
.Cast<string>()
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
_logger.LogInformation("COMPOSITE SCHEDULED: Trovati {Count} valori chiave nella sorgente", sourceKeyValues.Count);
|
||||
|
||||
// Sincronizza le cancellazioni
|
||||
var deletionOptions = new DeletionSyncOptions
|
||||
{
|
||||
Action = DeletionAction.Delete // Default: elimina fisicamente
|
||||
};
|
||||
|
||||
var deletionResult = await _deletionSyncService.SyncDeletionsAsync(
|
||||
sourceKeyValues,
|
||||
currentEntityName,
|
||||
currentCredentialName,
|
||||
restClient,
|
||||
deletionOptions);
|
||||
|
||||
if (deletionResult.DeletedRecordsDetected > 0)
|
||||
{
|
||||
_logger.LogInformation("COMPOSITE SCHEDULED: Sincronizzazione cancellazioni completata - {Detected} rilevati, {Synced} sincronizzati, {Errors} errori",
|
||||
deletionResult.DeletedRecordsDetected,
|
||||
deletionResult.DeletedRecordsSynced,
|
||||
deletionResult.SyncErrors);
|
||||
}
|
||||
}
|
||||
catch (Exception delEx)
|
||||
{
|
||||
_logger.LogError(delEx, "COMPOSITE SCHEDULED: Errore durante la sincronizzazione delle cancellazioni");
|
||||
}
|
||||
}
|
||||
|
||||
return totalProcessed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user