[Feature] Implementato sistema di valori default per campi mapping
- Creato modello FieldMappingEntry per gestione unificata di field mapping e default values - Aggiunta colonna DefaultValuesJson alla tabella DataCouplerProfile (max 4000 caratteri) - Implementata UI con toggle per selezionare modalità Mapping o Default - Supporto per 9 tipi di dati: string, int, long, decimal, double, float, boolean, datetime, datetimeoffset - Aggiornata logica TransformRecordToRestEntity per applicare valori default dopo field mapping - Implementata serializzazione/deserializzazione DefaultValues in DataCouplerProfileService - Sistema completo di salvataggio/caricamento valori default nei profili - Migrazione database AddDefaultValuesJsonToProfile creata e applicata
This commit is contained in:
@@ -25,6 +25,7 @@ public partial class ProfileSaver
|
||||
[Parameter] public string? DestinationTable { get; set; }
|
||||
[Parameter] public string? DestinationEndpoint { get; set; }
|
||||
[Parameter] public List<FieldMappingDto>? FieldMappings { get; set; }
|
||||
[Parameter] public Dictionary<string, (object? Value, string? Type)>? DefaultValues { get; set; }
|
||||
[Parameter] public List<ExternalIdRelationshipDto>? ExternalIdRelationships { get; set; }
|
||||
[Parameter] public string? SourceKeyField { get; set; }
|
||||
[Parameter] public bool UseRecordAssociations { get; set; }
|
||||
@@ -79,6 +80,7 @@ public partial class ProfileSaver
|
||||
DestinationTable = DestinationTable,
|
||||
DestinationEndpoint = DestinationEndpoint,
|
||||
FieldMappings = FieldMappings,
|
||||
DefaultValues = DefaultValues,
|
||||
ExternalIdRelationships = ExternalIdRelationships,
|
||||
SourceKeyField = SourceKeyField,
|
||||
UseRecordAssociations = UseRecordAssociations
|
||||
|
||||
Generated
+601
@@ -0,0 +1,601 @@
|
||||
// <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.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CredentialDbContext))]
|
||||
[Migration("20260216113009_AddDefaultValuesJsonToProfile")]
|
||||
partial class AddDefaultValuesJsonToProfile
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
|
||||
|
||||
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<string>("OdbcDsnName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("OdbcMode")
|
||||
.HasMaxLength(20)
|
||||
.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>("DefaultValuesJson")
|
||||
.HasMaxLength(4000)
|
||||
.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>("ExternalIdRelationshipsJson")
|
||||
.HasMaxLength(4000)
|
||||
.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.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDefaultValuesJsonToProfile : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DefaultValuesJson",
|
||||
table: "DataCouplerProfiles",
|
||||
type: "TEXT",
|
||||
maxLength: 4000,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DefaultValuesJson",
|
||||
table: "DataCouplerProfiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +146,10 @@ namespace CredentialManager.Migrations
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DefaultValuesJson")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeletionAction")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -60,6 +60,11 @@ public class DataCouplerProfile
|
||||
[MaxLength(4000)]
|
||||
public string? FieldMappingJson { get; set; }
|
||||
|
||||
// Default values per i campi di destinazione salvati come JSON
|
||||
// Formato: { "DestinationField": { "Value": "defaultValue", "Type": "string" } }
|
||||
[MaxLength(4000)]
|
||||
public string? DefaultValuesJson { get; set; }
|
||||
|
||||
// External ID Relationships per Salesforce salvate come JSON
|
||||
[MaxLength(4000)]
|
||||
public string? ExternalIdRelationshipsJson { get; set; }
|
||||
|
||||
@@ -30,6 +30,9 @@ public class DataCouplerProfileDto
|
||||
// Mapping dei campi
|
||||
public List<FieldMappingDto>? FieldMappings { get; set; }
|
||||
|
||||
// Default values per campi destinazione (FieldName -> (Value, Type))
|
||||
public Dictionary<string, (object? Value, string? Type)>? DefaultValues { get; set; }
|
||||
|
||||
// External ID Relationships per Salesforce
|
||||
public List<ExternalIdRelationshipDto>? ExternalIdRelationships { get; set; }
|
||||
|
||||
@@ -83,8 +86,15 @@ public class ExternalIdRelationshipDto
|
||||
public string SourceField { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO per la visualizzazione di un profilo nella lista
|
||||
/// <summary>/// DTO per i valori di default
|
||||
/// </summary>
|
||||
public class DefaultValueDto
|
||||
{
|
||||
public object? Value { get; set; }
|
||||
public string? Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>/// DTO per la visualizzazione di un profilo nella lista
|
||||
/// </summary>
|
||||
public class DataCouplerProfileSummaryDto
|
||||
{
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CredentialManager.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Tipo di mapping field
|
||||
/// </summary>
|
||||
public enum MappingType
|
||||
{
|
||||
/// <summary>
|
||||
/// Mapping da campo sorgente a campo destinazione
|
||||
/// </summary>
|
||||
FieldMapping,
|
||||
|
||||
/// <summary>
|
||||
/// Valore di default per campo destinazione
|
||||
/// </summary>
|
||||
DefaultValue
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rappresenta una voce di mapping che può essere:
|
||||
/// - Un mapping da campo sorgente a campo destinazione
|
||||
/// - Un valore di default per un campo destinazione
|
||||
/// </summary>
|
||||
public class FieldMappingEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Tipo di mapping
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public MappingType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nome del campo sorgente (solo per FieldMapping)
|
||||
/// </summary>
|
||||
[JsonPropertyName("sourceField")]
|
||||
public string? SourceField { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nome del campo destinazione
|
||||
/// </summary>
|
||||
[JsonPropertyName("destinationField")]
|
||||
public string DestinationField { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Valore di default (solo per DefaultValue)
|
||||
/// </summary>
|
||||
[JsonPropertyName("defaultValue")]
|
||||
public object? DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tipo di dato del valore di default (per conversioni corrette)
|
||||
/// Esempi: "string", "int", "decimal", "boolean", "datetime"
|
||||
/// </summary>
|
||||
[JsonPropertyName("defaultValueType")]
|
||||
public string? DefaultValueType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crea un mapping da campo sorgente a campo destinazione
|
||||
/// </summary>
|
||||
public static FieldMappingEntry CreateFieldMapping(string sourceField, string destinationField)
|
||||
{
|
||||
return new FieldMappingEntry
|
||||
{
|
||||
Type = MappingType.FieldMapping,
|
||||
SourceField = sourceField,
|
||||
DestinationField = destinationField
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea un valore di default per un campo destinazione
|
||||
/// </summary>
|
||||
public static FieldMappingEntry CreateDefaultValue(string destinationField, object defaultValue, string? valueType = null)
|
||||
{
|
||||
return new FieldMappingEntry
|
||||
{
|
||||
Type = MappingType.DefaultValue,
|
||||
DestinationField = destinationField,
|
||||
DefaultValue = defaultValue,
|
||||
DefaultValueType = valueType ?? InferValueType(defaultValue)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determina automaticamente il tipo del valore
|
||||
/// </summary>
|
||||
private static string InferValueType(object? value)
|
||||
{
|
||||
if (value == null) return "string";
|
||||
|
||||
return value switch
|
||||
{
|
||||
string _ => "string",
|
||||
int _ => "int",
|
||||
long _ => "long",
|
||||
decimal _ => "decimal",
|
||||
double _ => "double",
|
||||
float _ => "float",
|
||||
bool _ => "boolean",
|
||||
DateTime _ => "datetime",
|
||||
DateTimeOffset _ => "datetimeoffset",
|
||||
_ => "string"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene una descrizione user-friendly del mapping
|
||||
/// </summary>
|
||||
public string GetDescription()
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
MappingType.FieldMapping => $"{SourceField} → {DestinationField}",
|
||||
MappingType.DefaultValue => $"{DestinationField} = {DefaultValue ?? "null"} ({DefaultValueType})",
|
||||
_ => "Unknown"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper per la conversione tra vecchio formato (Dictionary) e nuovo formato (FieldMappingEntry)
|
||||
/// </summary>
|
||||
public static class MappingConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converte il vecchio formato Dictionary in lista di FieldMappingEntry
|
||||
/// </summary>
|
||||
public static List<FieldMappingEntry> FromDictionary(Dictionary<string, string> oldMappings)
|
||||
{
|
||||
var entries = new List<FieldMappingEntry>();
|
||||
|
||||
foreach (var mapping in oldMappings)
|
||||
{
|
||||
entries.Add(FieldMappingEntry.CreateFieldMapping(mapping.Key, mapping.Value));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converte una lista di FieldMappingEntry nel vecchio formato Dictionary (solo field mappings)
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> ToDictionary(List<FieldMappingEntry> entries)
|
||||
{
|
||||
var dictionary = new Dictionary<string, string>();
|
||||
|
||||
foreach (var entry in entries.Where(e => e.Type == MappingType.FieldMapping && !string.IsNullOrEmpty(e.SourceField)))
|
||||
{
|
||||
dictionary[entry.SourceField!] = entry.DestinationField;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene solo i valori di default da una lista di entries
|
||||
/// </summary>
|
||||
public static Dictionary<string, (object? Value, string? Type)> GetDefaultValues(List<FieldMappingEntry> entries)
|
||||
{
|
||||
var defaults = new Dictionary<string, (object?, string?)>();
|
||||
|
||||
foreach (var entry in entries.Where(e => e.Type == MappingType.DefaultValue))
|
||||
{
|
||||
defaults[entry.DestinationField] = (entry.DefaultValue, entry.DefaultValueType);
|
||||
}
|
||||
|
||||
return defaults;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +216,7 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Deserializza il JSON delle External ID Relationships
|
||||
/// </summary>
|
||||
@@ -237,6 +238,64 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializza i default values in JSON
|
||||
/// </summary>
|
||||
public string SerializeDefaultValues(Dictionary<string, (object? Value, string? Type)>? defaultValues)
|
||||
{
|
||||
if (defaultValues == null || !defaultValues.Any())
|
||||
return string.Empty;
|
||||
|
||||
// Converti in un formato serializzabile (Dictionary<string, DefaultValueDto>)
|
||||
var serializable = new Dictionary<string, DefaultValueDto>();
|
||||
foreach (var entry in defaultValues)
|
||||
{
|
||||
serializable[entry.Key] = new DefaultValueDto
|
||||
{
|
||||
Value = entry.Value.Value,
|
||||
Type = entry.Value.Type
|
||||
};
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(serializable, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializza il JSON dei default values
|
||||
/// </summary>
|
||||
public Dictionary<string, (object? Value, string? Type)> DeserializeDefaultValues(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return new Dictionary<string, (object?, string?)>();
|
||||
|
||||
try
|
||||
{
|
||||
var deserialized = JsonSerializer.Deserialize<Dictionary<string, DefaultValueDto>>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
|
||||
if (deserialized == null)
|
||||
return new Dictionary<string, (object?, string?)>();
|
||||
|
||||
// Converti nel formato tuple
|
||||
var result = new Dictionary<string, (object?, string?)>();
|
||||
foreach (var entry in deserialized)
|
||||
{
|
||||
result[entry.Key] = (entry.Value.Value, entry.Value.Type);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Dictionary<string, (object?, string?)>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converte un DataCouplerProfile in DTO
|
||||
/// </summary>
|
||||
@@ -262,6 +321,7 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
||||
DestinationTable = profile.DestinationTable,
|
||||
DestinationEndpoint = profile.DestinationEndpoint,
|
||||
FieldMappings = DeserializeFieldMappings(profile.FieldMappingJson),
|
||||
DefaultValues = DeserializeDefaultValues(profile.DefaultValuesJson),
|
||||
ExternalIdRelationships = DeserializeExternalIdRelationships(profile.ExternalIdRelationshipsJson),
|
||||
SourceKeyField = profile.SourceKeyField,
|
||||
UseRecordAssociations = profile.UseRecordAssociations
|
||||
@@ -291,6 +351,7 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
||||
DestinationTable = dto.DestinationTable,
|
||||
DestinationEndpoint = dto.DestinationEndpoint,
|
||||
FieldMappingJson = SerializeFieldMappings(dto.FieldMappings),
|
||||
DefaultValuesJson = SerializeDefaultValues(dto.DefaultValues),
|
||||
ExternalIdRelationshipsJson = SerializeExternalIdRelationships(dto.ExternalIdRelationships),
|
||||
SourceKeyField = dto.SourceKeyField,
|
||||
UseRecordAssociations = dto.UseRecordAssociations,
|
||||
|
||||
@@ -920,6 +920,25 @@
|
||||
<!-- Colonna Centrale: Controlli Mapping -->
|
||||
<div class="col-2 text-center">
|
||||
<div class="d-flex flex-column justify-content-center h-100">
|
||||
<!-- Toggle tra Mapping e Default Value -->
|
||||
<div class="btn-group mb-3" role="group">
|
||||
<button type="button"
|
||||
class="btn btn-sm @(isAddingDefaultValue ? "btn-outline-primary" : "btn-primary")"
|
||||
@onclick="@(() => isAddingDefaultValue = false)">
|
||||
<i class="fas fa-arrows-alt-h"></i>
|
||||
<small class="d-block">Mapping</small>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-sm @(isAddingDefaultValue ? "btn-warning" : "btn-outline-warning")"
|
||||
@onclick="@(() => isAddingDefaultValue = true)">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
<small class="d-block">Default</small>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Controlli per Mapping Normale -->
|
||||
@if (!isAddingDefaultValue)
|
||||
{
|
||||
<button class="btn btn-success mb-2" @onclick="CreateMapping"
|
||||
disabled="@(string.IsNullOrEmpty(selectedDbColumn) || string.IsNullOrEmpty(selectedRestProperty))">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
@@ -934,9 +953,47 @@
|
||||
<i class="fas fa-magic"></i>
|
||||
<small class="d-block">Auto</small>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Controlli per Default Value -->
|
||||
<div class="mb-2">
|
||||
<small class="text-muted d-block mb-1">Tipo Valore:</small>
|
||||
<select class="form-select form-select-sm mb-2" @bind="defaultValueType">
|
||||
<option value="string">String</option>
|
||||
<option value="int">Integer</option>
|
||||
<option value="decimal">Decimal</option>
|
||||
<option value="boolean">Boolean</option>
|
||||
<option value="datetime">DateTime</option>
|
||||
</select>
|
||||
<input type="text" class="form-control form-control-sm mb-2"
|
||||
placeholder="Valore default..."
|
||||
@bind="defaultValueInput" />
|
||||
<small class="text-muted d-block mb-2">
|
||||
@if (defaultValueType == "datetime")
|
||||
{
|
||||
<span>Es: @DateTime.Now.ToString("yyyy-MM-dd")</span>
|
||||
}
|
||||
else if (defaultValueType == "boolean")
|
||||
{
|
||||
<span>Es: true o false</span>
|
||||
}
|
||||
else if (defaultValueType == "decimal")
|
||||
{
|
||||
<span>Es: 100.50</span>
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
<button class="btn btn-warning mb-2" @onclick="CreateDefaultValue"
|
||||
disabled="@(string.IsNullOrEmpty(selectedRestProperty) || string.IsNullOrEmpty(defaultValueInput))">
|
||||
<i class="fas fa-check"></i>
|
||||
<small class="d-block">Set Default</small>
|
||||
</button>
|
||||
}
|
||||
|
||||
<button class="btn btn-secondary" @onclick="ClearAllMappings">
|
||||
<i class="fas fa-trash"></i>
|
||||
<small class="d-block">Clear</small>
|
||||
<small class="d-block">Clear All</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -965,6 +1022,10 @@
|
||||
{
|
||||
<span class="badge bg-success">Mapped</span>
|
||||
}
|
||||
@if (defaultValues.ContainsKey(property.Name))
|
||||
{
|
||||
<span class="badge bg-warning text-dark">Default</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@@ -1087,11 +1148,11 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Sezione Mappature Correnti --> @if (fieldMappings.Any())
|
||||
<!-- Sezione Mappature Correnti --> @if (fieldMappings.Any() || defaultValues.Any())
|
||||
{
|
||||
<div class="mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h6>Mappature Correnti (@fieldMappings.Count)</h6>
|
||||
<h6>Configurazione Mapping (@(fieldMappings.Count + defaultValues.Count) totali)</h6>
|
||||
@if (keyFields.Any())
|
||||
{
|
||||
<small class="text-info">
|
||||
@@ -1099,15 +1160,24 @@
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Tabella Mapping Campi -->
|
||||
@if (fieldMappings.Any())
|
||||
{
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-light">
|
||||
<i class="fas fa-arrows-alt-h"></i> <strong>Field Mappings</strong> (@fieldMappings.Count)
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<table class="table table-sm table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Campo Database</th>
|
||||
<th>Tipo DB</th>
|
||||
<th>Campo Sorgente</th>
|
||||
<th>Tipo Sorgente</th>
|
||||
<th>→</th>
|
||||
<th>Proprietà REST</th>
|
||||
<th>Tipo REST</th>
|
||||
<th>Campo Destinazione</th>
|
||||
<th>Tipo Destinazione</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1138,6 +1208,54 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Tabella Default Values -->
|
||||
@if (defaultValues.Any())
|
||||
{
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<i class="fas fa-file-alt"></i> <strong>Default Values</strong> (@defaultValues.Count)
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Campo Destinazione</th>
|
||||
<th>Valore Default</th>
|
||||
<th>Tipo Valore</th>
|
||||
<th>Tipo Campo REST</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var defaultValue in defaultValues)
|
||||
{
|
||||
var restProperty = restEntityDetails?.Properties.FirstOrDefault(p => p.Name == defaultValue.Key);
|
||||
var (value, valueType) = defaultValue.Value;
|
||||
<tr>
|
||||
<td><strong>@defaultValue.Key</strong></td>
|
||||
<td><code>@(value?.ToString() ?? "null")</code></td>
|
||||
<td>
|
||||
<span class="badge bg-info">@valueType</span>
|
||||
</td>
|
||||
<td><small class="text-muted">@(restProperty?.Type ?? "Unknown")</small></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-danger" @onclick="@(() => RemoveDefaultValue(defaultValue.Key))">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Configurazione Chiave Sorgente -->
|
||||
@@ -1314,6 +1432,7 @@
|
||||
DestinationCredentialName="@selectedRestCredential"
|
||||
DestinationEndpoint="@selectedRestEntity?.Name"
|
||||
FieldMappings="@GetCurrentFieldMappings()"
|
||||
DefaultValues="@defaultValues"
|
||||
ExternalIdRelationships="@externalIdRelationships"
|
||||
SourceKeyField="@sourceKeyField"
|
||||
UseRecordAssociations="@useRecordAssociations"
|
||||
|
||||
@@ -51,10 +51,18 @@ public partial class DataCoupler : ComponentBase
|
||||
(int)Math.Ceiling((double)fileData[sheetName].Count / pageSize) : 0;
|
||||
|
||||
// Mapping campi
|
||||
private Dictionary<string, string> fieldMappings = new(); // DbColumn -> RestProperty
|
||||
private Dictionary<string, string> fieldMappings = new(); // DbColumn -> RestProperty (legacy)
|
||||
private List<FieldMappingEntry> fieldMappingEntries = new(); // New system: supporta sia mapping che default values
|
||||
private Dictionary<string, (object? Value, string? Type)> defaultValues = new(); // DestinationField -> (DefaultValue, Type)
|
||||
private HashSet<string> keyFields = new(); // REST properties marked as keys
|
||||
private string selectedDbColumn = "";
|
||||
|
||||
// UI per configurazione mapping/default value
|
||||
private bool isAddingDefaultValue = false; // Toggle tra mapping normale e default value
|
||||
private string defaultValueField = ""; // Campo destinazione per default value
|
||||
private string defaultValueInput = ""; // Input utente per default value
|
||||
private string defaultValueType = "string"; // Tipo del default value (string, int, decimal, boolean, datetime)
|
||||
|
||||
// External ID Relationships (Salesforce)
|
||||
private List<ExternalIdRelationshipDto> externalIdRelationships = new();
|
||||
private string selectedRelationshipObject = "";
|
||||
@@ -345,11 +353,13 @@ public partial class DataCoupler : ComponentBase
|
||||
|
||||
// Applica i mapping
|
||||
fieldMappings.Clear();
|
||||
fieldMappingEntries.Clear();
|
||||
keyFields.Clear();
|
||||
|
||||
foreach (var mapping in mappings)
|
||||
{
|
||||
fieldMappings[mapping.SourceField] = mapping.DestinationField;
|
||||
fieldMappingEntries.Add(FieldMappingEntry.CreateFieldMapping(mapping.SourceField, mapping.DestinationField));
|
||||
if (mapping.IsKey)
|
||||
{
|
||||
keyFields.Add(mapping.DestinationField);
|
||||
@@ -371,6 +381,42 @@ public partial class DataCoupler : ComponentBase
|
||||
Logger.LogInformation("Nessun mapping campi da applicare");
|
||||
}
|
||||
|
||||
// Step 4.5: Applica default values se disponibili
|
||||
if (!string.IsNullOrEmpty(profile.DefaultValuesJson))
|
||||
{
|
||||
Logger.LogInformation("Step 4.5 - Applicazione default values...");
|
||||
try
|
||||
{
|
||||
var deserializedDefaults = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, DefaultValueDto>>(
|
||||
profile.DefaultValuesJson,
|
||||
new System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase });
|
||||
|
||||
if (deserializedDefaults != null)
|
||||
{
|
||||
defaultValues.Clear();
|
||||
|
||||
foreach (var entry in deserializedDefaults)
|
||||
{
|
||||
defaultValues[entry.Key] = (entry.Value.Value, entry.Value.Type);
|
||||
fieldMappingEntries.Add(FieldMappingEntry.CreateDefaultValue(entry.Key, entry.Value.Value, entry.Value.Type));
|
||||
|
||||
Logger.LogInformation("Default value applicato: {Field} = {Value} ({Type})",
|
||||
entry.Key, entry.Value.Value, entry.Value.Type);
|
||||
}
|
||||
|
||||
Logger.LogInformation("Default values applicati - Totale: {Count}", defaultValues.Count);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Errore nel caricamento dei default values dal profilo");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation("Nessun default value da applicare");
|
||||
}
|
||||
|
||||
// Step 5: Applica configurazione chiave sorgente
|
||||
if (!string.IsNullOrEmpty(profile.SourceKeyField))
|
||||
{
|
||||
@@ -721,6 +767,8 @@ public partial class DataCoupler : ComponentBase
|
||||
ResetSourceState();
|
||||
ResetDestinationState();
|
||||
fieldMappings.Clear();
|
||||
fieldMappingEntries.Clear();
|
||||
defaultValues.Clear();
|
||||
keyFields.Clear();
|
||||
externalIdRelationships.Clear(); // Reset relazioni
|
||||
transferResults.Clear();
|
||||
@@ -1328,6 +1376,17 @@ public partial class DataCoupler : ComponentBase
|
||||
// Crea il nuovo mapping
|
||||
fieldMappings[selectedDbColumn] = selectedRestProperty;
|
||||
|
||||
// Aggiorna anche la lista FieldMappingEntries
|
||||
var existingEntry = fieldMappingEntries.FirstOrDefault(e =>
|
||||
e.Type == CredentialManager.Models.MappingType.FieldMapping && e.SourceField == selectedDbColumn);
|
||||
|
||||
if (existingEntry != null)
|
||||
{
|
||||
fieldMappingEntries.Remove(existingEntry);
|
||||
}
|
||||
|
||||
fieldMappingEntries.Add(FieldMappingEntry.CreateFieldMapping(selectedDbColumn, selectedRestProperty));
|
||||
|
||||
Logger.LogInformation("Creato mapping: {DbColumn} -> {RestProperty}", selectedDbColumn, selectedRestProperty);
|
||||
|
||||
// Deseleziona i campi
|
||||
@@ -1335,14 +1394,108 @@ public partial class DataCoupler : ComponentBase
|
||||
selectedRestProperty = "";
|
||||
}
|
||||
|
||||
private void CreateDefaultValue()
|
||||
{
|
||||
if (string.IsNullOrEmpty(selectedRestProperty) || string.IsNullOrEmpty(defaultValueInput))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// Converti il valore nel tipo appropriato
|
||||
object? convertedValue = ConvertDefaultValue(defaultValueInput, defaultValueType);
|
||||
|
||||
// Rimuovi eventuale default value esistente per questo campo
|
||||
if (defaultValues.ContainsKey(selectedRestProperty))
|
||||
{
|
||||
defaultValues.Remove(selectedRestProperty);
|
||||
}
|
||||
|
||||
// Rimuovi anche dalla lista entries
|
||||
var existingEntry = fieldMappingEntries.FirstOrDefault(e =>
|
||||
e.Type == CredentialManager.Models.MappingType.DefaultValue && e.DestinationField == selectedRestProperty);
|
||||
|
||||
if (existingEntry != null)
|
||||
{
|
||||
fieldMappingEntries.Remove(existingEntry);
|
||||
}
|
||||
|
||||
// Aggiungi il nuovo default value
|
||||
defaultValues[selectedRestProperty] = (convertedValue, defaultValueType);
|
||||
fieldMappingEntries.Add(FieldMappingEntry.CreateDefaultValue(selectedRestProperty, convertedValue, defaultValueType));
|
||||
|
||||
Logger.LogInformation("Creato default value: {RestProperty} = {Value} ({Type})",
|
||||
selectedRestProperty, convertedValue, defaultValueType);
|
||||
|
||||
// Reset campi
|
||||
selectedRestProperty = "";
|
||||
defaultValueInput = "";
|
||||
isAddingDefaultValue = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore nella conversione del valore di default");
|
||||
transferMessage = $"Errore: {ex.Message}";
|
||||
transferMessageType = "error";
|
||||
}
|
||||
}
|
||||
|
||||
private object? ConvertDefaultValue(string input, string type)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return null;
|
||||
|
||||
return type.ToLower() switch
|
||||
{
|
||||
"string" => input,
|
||||
"int" => int.Parse(input),
|
||||
"long" => long.Parse(input),
|
||||
"decimal" => decimal.Parse(input, System.Globalization.CultureInfo.InvariantCulture),
|
||||
"double" => double.Parse(input, System.Globalization.CultureInfo.InvariantCulture),
|
||||
"float" => float.Parse(input, System.Globalization.CultureInfo.InvariantCulture),
|
||||
"boolean" => bool.Parse(input),
|
||||
"datetime" => DateTime.Parse(input),
|
||||
"datetimeoffset" => DateTimeOffset.Parse(input),
|
||||
_ => input
|
||||
};
|
||||
}
|
||||
|
||||
private void RemoveMapping()
|
||||
{
|
||||
if (string.IsNullOrEmpty(selectedDbColumn) || !fieldMappings.ContainsKey(selectedDbColumn))
|
||||
return;
|
||||
|
||||
fieldMappings.Remove(selectedDbColumn);
|
||||
|
||||
// Rimuovi anche dalla lista entries
|
||||
var entry = fieldMappingEntries.FirstOrDefault(e =>
|
||||
e.Type == CredentialManager.Models.MappingType.FieldMapping && e.SourceField == selectedDbColumn);
|
||||
if (entry != null)
|
||||
{
|
||||
fieldMappingEntries.Remove(entry);
|
||||
}
|
||||
|
||||
Logger.LogInformation("Rimosso mapping per campo: {DbColumn}", selectedDbColumn);
|
||||
}
|
||||
|
||||
private void RemoveDefaultValue(string destinationField)
|
||||
{
|
||||
if (defaultValues.ContainsKey(destinationField))
|
||||
{
|
||||
defaultValues.Remove(destinationField);
|
||||
|
||||
// Rimuovi anche dalla lista entries
|
||||
var entry = fieldMappingEntries.FirstOrDefault(e =>
|
||||
e.Type == CredentialManager.Models.MappingType.DefaultValue && e.DestinationField == destinationField);
|
||||
if (entry != null)
|
||||
{
|
||||
fieldMappingEntries.Remove(entry);
|
||||
}
|
||||
|
||||
Logger.LogInformation("Rimosso default value per campo: {Field}", destinationField);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSpecificMapping(string dbColumn)
|
||||
{
|
||||
if (fieldMappings.ContainsKey(dbColumn))
|
||||
@@ -1351,20 +1504,22 @@ public partial class DataCoupler : ComponentBase
|
||||
Logger.LogInformation("Rimosso mapping specifico per campo: {DbColumn}", dbColumn);
|
||||
}
|
||||
}
|
||||
Logger.LogInformation("Rimosso mapping specifico per campo: {DbColumn}", dbColumn);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllMappings()
|
||||
{
|
||||
fieldMappings.Clear();
|
||||
fieldMappingEntries.Clear();
|
||||
defaultValues.Clear();
|
||||
selectedDbColumn = "";
|
||||
selectedRestProperty = "";
|
||||
sourceKeyField = "";
|
||||
transferMessage = "";
|
||||
transferMessageType = "";
|
||||
isAddingDefaultValue = false;
|
||||
defaultValueField = "";
|
||||
defaultValueInput = "";
|
||||
externalIdRelationships.Clear(); // Pulisce anche le relazioni
|
||||
Logger.LogInformation("Tutti i mapping e le configurazioni sono stati cancellati");
|
||||
Logger.LogInformation("Tutti i mapping, default values e le configurazioni sono stati cancellati");
|
||||
}
|
||||
|
||||
// External ID Relationships Methods
|
||||
@@ -2108,6 +2263,7 @@ public partial class DataCoupler : ComponentBase
|
||||
.Select(r => r.SourceField)
|
||||
.ToHashSet();
|
||||
|
||||
// STEP 1: Applica i mapping normali (campo sorgente -> campo destinazione)
|
||||
foreach (var mapping in fieldMappings)
|
||||
{
|
||||
string dbColumn = mapping.Key;
|
||||
@@ -2134,7 +2290,29 @@ public partial class DataCoupler : ComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
// Aggiungi External ID Relationships (per Salesforce)
|
||||
// STEP 2: Applica i valori di default per i campi NON ancora popolati
|
||||
foreach (var defaultValue in defaultValues)
|
||||
{
|
||||
string destinationField = defaultValue.Key;
|
||||
var (value, valueType) = defaultValue.Value;
|
||||
|
||||
// Applica il default value solo se il campo non è già stato popolato dal mapping
|
||||
if (!restData.ContainsKey(destinationField))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
restData[destinationField] = value;
|
||||
Logger.LogDebug("Applicato default value: {Field} = {Value} ({Type})",
|
||||
destinationField, value, valueType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug("Campo {Field} già popolato da mapping, default value ignorato", destinationField);
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 3: Aggiungi External ID Relationships (per Salesforce)
|
||||
if (externalIdRelationships.Any())
|
||||
{
|
||||
foreach (var relationship in externalIdRelationships)
|
||||
@@ -2163,9 +2341,10 @@ public partial class DataCoupler : ComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogDebug("Record trasformato: {DbColumns} → {RestProperties}",
|
||||
Logger.LogDebug("Record trasformato: {DbColumns} → {RestProperties} (inclusi {DefaultCount} default values)",
|
||||
string.Join(", ", dbRecord.Keys),
|
||||
string.Join(", ", restData.Keys));
|
||||
string.Join(", ", restData.Keys),
|
||||
defaultValues.Count(dv => restData.ContainsKey(dv.Key)));
|
||||
|
||||
return restData;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user