Aggiornamento staging #1
@@ -145,6 +145,7 @@
|
|||||||
- **Storico Esecuzioni**: Log completo con timestamp, record processati, errori
|
- **Storico Esecuzioni**: Log completo con timestamp, record processati, errori
|
||||||
- **Pausa/Riprendi**: Controllo dinamico schedulazioni
|
- **Pausa/Riprendi**: Controllo dinamico schedulazioni
|
||||||
- **Override Database**: Possibilità di sovrascrivere sorgente/destinazione
|
- **Override Database**: Possibilità di sovrascrivere sorgente/destinazione
|
||||||
|
- **Deletion Sync Configurabile**: Opzione per abilitare sincronizzazione eliminazioni (disabilitata di default)
|
||||||
|
|
||||||
#### File Chiave:
|
#### File Chiave:
|
||||||
- `CredentialManager/Models/ProfileSchedule.cs`
|
- `CredentialManager/Models/ProfileSchedule.cs`
|
||||||
@@ -191,9 +192,23 @@
|
|||||||
- **Gestione Associazioni**: Aggiorna/elimina associazioni correlate
|
- **Gestione Associazioni**: Aggiorna/elimina associazioni correlate
|
||||||
- **Modalità Sicura**: Preview eliminazioni prima dell'esecuzione
|
- **Modalità Sicura**: Preview eliminazioni prima dell'esecuzione
|
||||||
- **Logging Completo**: Traccia tutte le operazioni di eliminazione
|
- **Logging Completo**: Traccia tutte le operazioni di eliminazione
|
||||||
|
- **Configurazione Granulare**:
|
||||||
|
- **Disabilitata** completamente nei trasferimenti manuali (DataCoupler.razor)
|
||||||
|
- **Configurabile** nelle schedulazioni tramite flag `EnableDeletionSync`
|
||||||
|
- **Default: false** per massima sicurezza
|
||||||
|
- Warning esplicito nell'UI per operazioni critiche
|
||||||
|
|
||||||
|
#### Sicurezza:
|
||||||
|
- La funzionalità è **disabilitata di default** per evitare eliminazioni accidentali
|
||||||
|
- Disponibile **solo per le schedulazioni** con configurazione esplicita
|
||||||
|
- L'utente deve attivamente abilitare la funzione con piena consapevolezza
|
||||||
|
- Logging completo di tutte le operazioni di eliminazione per audit trail
|
||||||
|
|
||||||
#### File Chiave:
|
#### File Chiave:
|
||||||
- `Data_Coupler/Services/DeletionSyncService.cs`
|
- `Data_Coupler/Services/DeletionSyncService.cs`
|
||||||
|
- `Data_Coupler/Services/ScheduledProfileExecutionService.cs`
|
||||||
|
- `CredentialManager/Models/ProfileSchedule.cs` (campo `EnableDeletionSync`)
|
||||||
|
- `Data_Coupler/Pages/Scheduling.razor` (UI configurazione)
|
||||||
|
|
||||||
### 8. Sistema di Autenticazione
|
### 8. Sistema di Autenticazione
|
||||||
|
|
||||||
|
|||||||
Generated
+585
@@ -0,0 +1,585 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using CredentialManager.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CredentialManager.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CredentialDbContext))]
|
||||||
|
[Migration("20260123104841_AddEnableDeletionSyncToProfileSchedule")]
|
||||||
|
partial class AddEnableDeletionSyncToProfileSchedule
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.CredentialEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AdditionalParameters")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("CommandTimeout")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(30);
|
||||||
|
|
||||||
|
b.Property<string>("ConnectionString")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DatabaseName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DatabaseType")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptedApiKey")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptedAuthToken")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptedPassword")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Headers")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Host")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IgnoreSslErrors")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("Port")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("RestServiceType")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TimeoutSeconds")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(100);
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DatabaseType");
|
||||||
|
|
||||||
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("Type");
|
||||||
|
|
||||||
|
b.ToTable("Credentials", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DeletionAction")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DeletionMarkField")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DeletionMarkValue")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("DestinationCredentialId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationEndpoint")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationSchema")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationTable")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("FieldMappingJson")
|
||||||
|
.HasMaxLength(4000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastUsedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("SourceCredentialId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SourceCustomQuery")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceDatabaseName")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceFilePath")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceKeyField")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceSchema")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceTable")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("SyncDeletions")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("UseRecordAssociations")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedAt");
|
||||||
|
|
||||||
|
b.HasIndex("DestinationCredentialId");
|
||||||
|
|
||||||
|
b.HasIndex("DestinationType");
|
||||||
|
|
||||||
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("LastUsedAt");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SourceCredentialId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceType");
|
||||||
|
|
||||||
|
b.ToTable("DataCouplerProfiles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.KeyAssociation", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AdditionalInfo")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Data_Hash")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("DeletionSynced")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletionSyncedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationEntity")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationKeyField")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsSourceDeleted")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("KeyValue")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastVerifiedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("MappedDestinationField")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RestCredentialName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceKeyField")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourcesInfo")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedAt");
|
||||||
|
|
||||||
|
b.HasIndex("DestinationEntity");
|
||||||
|
|
||||||
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("KeyValue")
|
||||||
|
.HasDatabaseName("IX_KeyAssociations_KeyValue");
|
||||||
|
|
||||||
|
b.HasIndex("LastVerifiedAt");
|
||||||
|
|
||||||
|
b.HasIndex("RestCredentialName");
|
||||||
|
|
||||||
|
b.HasIndex("KeyValue", "DestinationEntity", "RestCredentialName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("IX_KeyAssociations_Unique");
|
||||||
|
|
||||||
|
b.ToTable("KeyAssociations", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.ProfileSchedule", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DailyTime")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("DayOfMonth")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("DayOfWeek")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationDatabaseOverride")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableDeletionSync")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ExecutionCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("IntervalUnit")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("IntervalValue")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("LastExecutionMessage")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("LastExecutionRecordCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("LastExecutionStatus")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastExecutionTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("NextExecutionTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ProfileId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ScheduleType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ScheduledDateTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceDatabaseOverride")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProfileId");
|
||||||
|
|
||||||
|
b.ToTable("ProfileSchedules");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.ScheduleExecutionHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AdditionalInfo")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationInfo")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationType")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("EndTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ErrorDetails")
|
||||||
|
.HasMaxLength(5000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Message")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ProfileId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ProfileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("RecordsProcessed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("RecordsWithErrors")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ScheduleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SourceInfo")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceType")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TriggerType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TriggeredBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProfileId");
|
||||||
|
|
||||||
|
b.HasIndex("ScheduleId");
|
||||||
|
|
||||||
|
b.HasIndex("StartTime");
|
||||||
|
|
||||||
|
b.HasIndex("Status");
|
||||||
|
|
||||||
|
b.HasIndex("TriggerType");
|
||||||
|
|
||||||
|
b.ToTable("ScheduleExecutionHistories", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CredentialManager.Models.CredentialEntity", "DestinationCredential")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DestinationCredentialId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("CredentialManager.Models.CredentialEntity", "SourceCredential")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceCredentialId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("DestinationCredential");
|
||||||
|
|
||||||
|
b.Navigation("SourceCredential");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.ProfileSchedule", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CredentialManager.Models.DataCouplerProfile", "Profile")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProfileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Profile");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.ScheduleExecutionHistory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CredentialManager.Models.ProfileSchedule", "Schedule")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ScheduleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Schedule");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CredentialManager.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddEnableDeletionSyncToProfileSchedule : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "EnableDeletionSync",
|
||||||
|
table: "ProfileSchedules",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "EnableDeletionSync",
|
||||||
|
table: "ProfileSchedules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -382,6 +382,9 @@ namespace CredentialManager.Migrations
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableDeletionSync")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("ExecutionCount")
|
b.Property<int>("ExecutionCount")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ public class ProfileSchedule
|
|||||||
[MaxLength(100)]
|
[MaxLength(100)]
|
||||||
public string? DestinationDatabaseOverride { get; set; }
|
public string? DestinationDatabaseOverride { get; set; }
|
||||||
|
|
||||||
|
// Configurazione sincronizzazione eliminazioni (default: disabilitata)
|
||||||
|
public bool EnableDeletionSync { get; set; } = false;
|
||||||
|
|
||||||
// Metadati
|
// Metadati
|
||||||
[MaxLength(100)]
|
[MaxLength(100)]
|
||||||
public string? CreatedBy { get; set; }
|
public string? CreatedBy { get; set; }
|
||||||
|
|||||||
Binary file not shown.
@@ -289,6 +289,9 @@ public class ProfileScheduleBackup
|
|||||||
|
|
||||||
[JsonPropertyName("createdBy")]
|
[JsonPropertyName("createdBy")]
|
||||||
public string? CreatedBy { get; set; }
|
public string? CreatedBy { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("enableDeletionSync")]
|
||||||
|
public bool EnableDeletionSync { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1496,8 +1496,10 @@ public partial class DataCoupler : ComponentBase
|
|||||||
recordNumber++;
|
recordNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3.5 Sincronizza le cancellazioni (se abilitato)
|
// 3.5 Sincronizzazione cancellazioni (DISABILITATA per trasferimenti manuali)
|
||||||
|
// Questa funzionalità è disponibile solo per le schedulazioni con configurazione esplicita
|
||||||
int deletedCount = 0;
|
int deletedCount = 0;
|
||||||
|
/* DELETION SYNC DISABILITATA PER TRASFERIMENTI MANUALI
|
||||||
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKeyField))
|
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKeyField))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1570,6 +1572,7 @@ public partial class DataCoupler : ComponentBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// 4. Mostra risultati
|
// 4. Mostra risultati
|
||||||
if (errorCount == 0)
|
if (errorCount == 0)
|
||||||
|
|||||||
@@ -336,6 +336,30 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i> Opzioni Avanzate
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<InputCheckbox @bind-Value="editingSchedule.EnableDeletionSync" class="form-check-input" id="enableDeletionSyncCheckbox" />
|
||||||
|
<label class="form-check-label" for="enableDeletionSyncCheckbox">
|
||||||
|
<strong>Abilita sincronizzazione eliminazioni</strong>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning mb-0">
|
||||||
|
<small>
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<strong>Attenzione:</strong> Se abilitata, i record eliminati dalla sorgente saranno automaticamente eliminati anche dalla destinazione durante l'esecuzione schedulata.
|
||||||
|
Questa opzione è <strong>disabilitata di default</strong> per motivi di sicurezza.
|
||||||
|
Usare con cautela!
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2">
|
<div class="d-flex justify-content-end gap-2">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
|
|||||||
@@ -21,4 +21,11 @@ public interface IScheduledProfileExecutionService
|
|||||||
/// Esegue un profilo Data Coupler specificato dall'ID
|
/// Esegue un profilo Data Coupler specificato dall'ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<ProfileExecutionResult> ExecuteProfileAsync(int profileId);
|
Task<ProfileExecutionResult> ExecuteProfileAsync(int profileId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Esegue un profilo Data Coupler specificato dall'ID con configurazione sincronizzazione eliminazioni
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profileId">ID del profilo da eseguire</param>
|
||||||
|
/// <param name="enableDeletionSync">Se true, sincronizza le eliminazioni dalla sorgente alla destinazione</param>
|
||||||
|
Task<ProfileExecutionResult> ExecuteProfileAsync(int profileId, bool enableDeletionSync);
|
||||||
}
|
}
|
||||||
@@ -69,11 +69,11 @@ public class ScheduledExecutionBackgroundService : BackgroundService
|
|||||||
{
|
{
|
||||||
if (ShouldExecuteSchedule(schedule, currentTime))
|
if (ShouldExecuteSchedule(schedule, currentTime))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Esecuzione schedulata per profilo: {ProfileName} (Schedule: {ScheduleName})",
|
_logger.LogInformation("Esecuzione schedulata per profilo: {ProfileName} (Schedule: {ScheduleName}) - DeletionSync: {DeletionSync}",
|
||||||
schedule.Profile?.Name ?? "N/A", schedule.Name);
|
schedule.Profile?.Name ?? "N/A", schedule.Name, schedule.EnableDeletionSync);
|
||||||
|
|
||||||
// Esegui il profilo
|
// Esegui il profilo con il flag deletion sync dalla schedulazione
|
||||||
var result = await executionService.ExecuteProfileAsync(schedule.ProfileId);
|
var result = await executionService.ExecuteProfileAsync(schedule.ProfileId, schedule.EnableDeletionSync);
|
||||||
|
|
||||||
// Aggiorna la schedulazione
|
// Aggiorna la schedulazione
|
||||||
await UpdateScheduleAfterExecution(scheduleService, schedule, currentTime, result.Success);
|
await UpdateScheduleAfterExecution(scheduleService, schedule, currentTime, result.Success);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
private readonly IDataConnectionCredentialService _dataConnectionCredentialService;
|
private readonly IDataConnectionCredentialService _dataConnectionCredentialService;
|
||||||
private readonly IKeyAssociationService _keyAssociationService;
|
private readonly IKeyAssociationService _keyAssociationService;
|
||||||
private readonly IAssociationService _associationService;
|
private readonly IAssociationService _associationService;
|
||||||
|
private readonly IDeletionSyncService _deletionSyncService;
|
||||||
private readonly ILogger<ScheduledProfileExecutionService> _logger;
|
private readonly ILogger<ScheduledProfileExecutionService> _logger;
|
||||||
|
|
||||||
public ScheduledProfileExecutionService(
|
public ScheduledProfileExecutionService(
|
||||||
@@ -37,6 +38,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
IDataConnectionCredentialService dataConnectionCredentialService,
|
IDataConnectionCredentialService dataConnectionCredentialService,
|
||||||
IKeyAssociationService keyAssociationService,
|
IKeyAssociationService keyAssociationService,
|
||||||
IAssociationService associationService,
|
IAssociationService associationService,
|
||||||
|
IDeletionSyncService deletionSyncService,
|
||||||
ILogger<ScheduledProfileExecutionService> logger)
|
ILogger<ScheduledProfileExecutionService> logger)
|
||||||
{
|
{
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
@@ -45,6 +47,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
_dataConnectionCredentialService = dataConnectionCredentialService;
|
_dataConnectionCredentialService = dataConnectionCredentialService;
|
||||||
_keyAssociationService = keyAssociationService;
|
_keyAssociationService = keyAssociationService;
|
||||||
_associationService = associationService;
|
_associationService = associationService;
|
||||||
|
_deletionSyncService = deletionSyncService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +55,14 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
/// Esegue un profilo Data Coupler specificato dall'ID
|
/// Esegue un profilo Data Coupler specificato dall'ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<ProfileExecutionResult> ExecuteProfileAsync(int profileId)
|
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 startTime = DateTime.UtcNow;
|
||||||
var result = new ProfileExecutionResult
|
var result = new ProfileExecutionResult
|
||||||
@@ -61,7 +72,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
|
|
||||||
try
|
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
|
// Carica il profilo
|
||||||
var profile = await _profileService.GetProfileByIdAsync(profileId);
|
var profile = await _profileService.GetProfileByIdAsync(profileId);
|
||||||
@@ -78,7 +89,7 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
await _profileService.UpdateLastUsedAsync(profile.Id);
|
await _profileService.UpdateLastUsedAsync(profile.Id);
|
||||||
|
|
||||||
// Esegue il trasferimento dati con la logica completa
|
// Esegue il trasferimento dati con la logica completa
|
||||||
var recordsTransferred = await ExecuteDataTransferAsync(profile);
|
var recordsTransferred = await ExecuteDataTransferAsync(profile, enableDeletionSync);
|
||||||
|
|
||||||
result.Success = true;
|
result.Success = true;
|
||||||
result.RecordsProcessed = recordsTransferred;
|
result.RecordsProcessed = recordsTransferred;
|
||||||
@@ -106,10 +117,11 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
/// Metodo principale per l'esecuzione del trasferimento dati
|
/// Metodo principale per l'esecuzione del trasferimento dati
|
||||||
/// Implementa la stessa logica di StartDataTransferWithComposite
|
/// Implementa la stessa logica di StartDataTransferWithComposite
|
||||||
/// </summary>
|
/// </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("=== 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
|
try
|
||||||
{
|
{
|
||||||
@@ -151,12 +163,12 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
if (useSalesforceComposite)
|
if (useSalesforceComposite)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Utilizzo Salesforce Composite API per il trasferimento");
|
_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
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Utilizzo metodo trasferimento standard per il trasferimento");
|
_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)
|
catch (Exception ex)
|
||||||
@@ -389,9 +401,11 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
IRestServiceClient restClient,
|
IRestServiceClient restClient,
|
||||||
RestEntitySummary restEntity,
|
RestEntitySummary restEntity,
|
||||||
RestApiCredential restCredential,
|
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 successCount = 0;
|
||||||
int errorCount = 0;
|
int errorCount = 0;
|
||||||
@@ -454,6 +468,50 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
_logger.LogInformation("Trasferimento completato. Successi: {SuccessCount}, Errori: {ErrorCount}",
|
_logger.LogInformation("Trasferimento completato. Successi: {SuccessCount}, Errori: {ErrorCount}",
|
||||||
successCount, 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;
|
return successCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,15 +525,17 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
IRestServiceClient restClient,
|
IRestServiceClient restClient,
|
||||||
RestEntitySummary restEntity,
|
RestEntitySummary restEntity,
|
||||||
RestApiCredential restCredential,
|
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
|
// Verifica che sia effettivamente un SalesforceServiceClient
|
||||||
if (!(restClient is DataConnection.REST.Implementations.SalesforceServiceClient salesforceClient))
|
if (!(restClient is DataConnection.REST.Implementations.SalesforceServiceClient salesforceClient))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Client REST non è SalesforceServiceClient, fallback al metodo standard");
|
_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
|
try
|
||||||
@@ -738,6 +798,50 @@ public class ScheduledProfileExecutionService : IScheduledProfileExecutionServic
|
|||||||
_logger.LogInformation("COMPOSITE SCHEDULED: Trasferimento completato. Creazioni: {SuccessCount}, Aggiornamenti: {UpdatedCount}, Saltati: {SkippedCount}, Errori: {ErrorCount}",
|
_logger.LogInformation("COMPOSITE SCHEDULED: Trasferimento completato. Creazioni: {SuccessCount}, Aggiornamenti: {UpdatedCount}, Saltati: {SkippedCount}, Errori: {ErrorCount}",
|
||||||
successCount, updatedCount, skippedCount, 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;
|
return totalProcessed;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ docker build -t data-coupler:local-windows -f Dockerfile.windows .
|
|||||||
- **Validazione**: Validazione completa dei dati in input
|
- **Validazione**: Validazione completa dei dati in input
|
||||||
- **Isolamento**: Ogni progetto ha responsabilità specifiche
|
- **Isolamento**: Ogni progetto ha responsabilità specifiche
|
||||||
- **Type Safety**: Uso di tipi forti per evitare errori
|
- **Type Safety**: Uso di tipi forti per evitare errori
|
||||||
|
- **Deletion Sync Sicuro**:
|
||||||
|
- Disabilitato di default per prevenire eliminazioni accidentali
|
||||||
|
- Disponibile solo nelle schedulazioni con configurazione esplicita
|
||||||
|
- Warning chiaro nell'interfaccia utente per operazioni critiche
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user