fix: Correzione salvataggio campo MappedDestinationField in KeyAssociations

- Aggiunto campo MappedDestinationField al modello KeyAssociation per tracciare il campo destinazione mappato alla chiave sorgente
- Creata migration AddMappedDestinationFieldToKeyAssociation per aggiungere la colonna al database
- Implementata logica di popolamento in CreateAssociationAsync e StartDataTransferOriginal per salvare il campo destinazione mappato
- Aggiornato SaveAssociationParallelAsync per includere MappedDestinationField nelle query SQL UPDATE e INSERT
- Corretti indici parametri nella query UPDATE (da {7-9} a {8-10}) per includere il nuovo campo
- Aggiunta visualizzazione campo nell'interfaccia KeyAssociations (tabella, dettagli, export CSV)
- Implementato controllo validazione per impedire trasferimenti se il campo chiave non è mappato
- Aggiunto logging diagnostico dettagliato per debug del mapping dei campi
- Aggiornato ScheduledProfileExecutionService per popolare MappedDestinationField nelle esecuzioni schedulate
- Rimosso file BackgroundServices.cs obsoleto
- Documentazione completa creata (4 markdown files)

Fixes: Campo MappedDestinationField rimaneva NULL perché le query SQL raw non includevano il nuovo campo
This commit is contained in:
2025-10-20 00:42:07 +02:00
parent 22c0a15b8e
commit 5d9b9756cf
19 changed files with 2433 additions and 48 deletions
+72 -4
View File
@@ -1396,12 +1396,32 @@ public partial class DataCoupler : ComponentBase
{
// Determina i campi chiave automaticamente
var destinationKeyField = GetEntityIdField(); // Campo chiave nella destinazione
// Trova il campo destinazione (REST API) mappato al campo chiave sorgente
string? mappedDestinationField = null;
Logger.LogDebug("MAPPING DEBUG: Cercando il campo destinazione mappato al campo chiave sorgente '{SourceKeyField}'", sourceKeyField);
Logger.LogDebug("MAPPING DEBUG: Mappings disponibili: {Mappings}", string.Join(", ", fieldMappings.Select(m => $"{m.Key} -> {m.Value}")));
// Cerca nel dizionario il campo destinazione corrispondente al campo chiave sorgente
if (fieldMappings.TryGetValue(sourceKeyField, out var destinationFieldName))
{
mappedDestinationField = destinationFieldName;
Logger.LogDebug("MAPPING DEBUG: Trovato mapping: campo sorgente '{SourceField}' è mappato al campo destinazione '{DestField}'",
sourceKeyField, mappedDestinationField);
}
else
{
Logger.LogWarning("MAPPING DEBUG: Campo chiave sorgente '{SourceKeyField}' NON trovato nei mappings! Il campo MappedDestinationField non verrà popolato.",
sourceKeyField);
}
var association = new KeyAssociation
{
KeyValue = sourceKey,
SourceKeyField = sourceKeyField,
DestinationKeyField = destinationKeyField,
MappedDestinationField = mappedDestinationField, // Campo destinazione mappato al campo chiave sorgente
DestinationEntity = selectedRestEntity?.Name ?? "",
DestinationId = transferResult.EntityId,
RestCredentialName = selectedRestCredential,
@@ -1416,8 +1436,8 @@ public partial class DataCoupler : ComponentBase
})
};
Logger.LogInformation("ASSOCIATION DEBUG: Creazione nuova associazione - KeyValue: '{KeyValue}', Entity: '{Entity}', DestinationId: '{DestinationId}', Credential: '{Credential}'",
sourceKey, selectedRestEntity?.Name ?? "Unknown", transferResult.EntityId, selectedRestCredential);
Logger.LogInformation("ASSOCIATION DEBUG: Creazione nuova associazione - KeyValue: '{KeyValue}', Entity: '{Entity}', DestinationId: '{DestinationId}', Credential: '{Credential}', MappedField: '{MappedField}'",
sourceKey, selectedRestEntity?.Name ?? "Unknown", transferResult.EntityId, selectedRestCredential, mappedDestinationField ?? "N/A");
var associationId = await CredentialService.SaveKeyAssociationAsync(association);
Logger.LogInformation("DEBUG: Associazione salvata con ID: {AssociationId}", associationId);
@@ -1721,9 +1741,36 @@ public partial class DataCoupler : ComponentBase
if (useRecordAssociations && string.IsNullOrEmpty(sourceKeyField))
return false;
// Verifica che il campo chiave sia presente nei campi mappati
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKeyField))
{
if (!fieldMappings.ContainsKey(sourceKeyField))
return false;
}
return true;
}
/// <summary>
/// Ottiene il messaggio di errore che spiega perché il trasferimento non può essere avviato
/// </summary>
private string GetTransferDisabledReason()
{
if (!fieldMappings.Any())
return "Nessun campo mappato. Crea almeno un mapping tra i campi sorgente e destinazione.";
if (useRecordAssociations && string.IsNullOrEmpty(sourceKeyField))
return "Campo chiave sorgente non selezionato. Seleziona un campo che identifichi univocamente i record.";
if (useRecordAssociations && !string.IsNullOrEmpty(sourceKeyField))
{
if (!fieldMappings.ContainsKey(sourceKeyField))
return $"Il campo chiave '{sourceKeyField}' deve essere mappato. Crea un mapping per questo campo prima di procedere con il trasferimento.";
}
return string.Empty;
}
// Helper methods per UI risultati
private string GetResultRowClass(string status)
{
@@ -2844,11 +2891,32 @@ public partial class DataCoupler : ComponentBase
var finalDataHash = dataHash ?? GenerateDataHash(originalRecord);
var destinationKeyField = GetEntityIdField();
// Trova il campo destinazione (REST API) mappato al campo chiave sorgente
string? mappedDestinationField = null;
Logger.LogDebug("MAPPING DEBUG: Cercando il campo destinazione mappato al campo chiave sorgente '{SourceKeyField}'", currentSourceKeyField);
Logger.LogDebug("MAPPING DEBUG: Mappings disponibili: {Mappings}", string.Join(", ", fieldMappings.Select(m => $"{m.Key} -> {m.Value}")));
// Cerca nel dizionario il campo destinazione corrispondente al campo chiave sorgente
if (fieldMappings.TryGetValue(currentSourceKeyField, out var destinationFieldName))
{
mappedDestinationField = destinationFieldName;
Logger.LogDebug("MAPPING DEBUG: Trovato mapping: campo sorgente '{SourceField}' è mappato al campo destinazione '{DestField}'",
currentSourceKeyField, mappedDestinationField);
}
else
{
Logger.LogWarning("MAPPING DEBUG: Campo chiave sorgente '{SourceKeyField}' NON trovato nei mappings! Il campo MappedDestinationField non verrà popolato.",
currentSourceKeyField);
}
var association = new KeyAssociation
{
KeyValue = sourceKey,
SourceKeyField = currentSourceKeyField,
DestinationKeyField = destinationKeyField,
MappedDestinationField = mappedDestinationField, // Campo destinazione mappato al campo chiave sorgente
DestinationEntity = currentEntityName,
DestinationId = entityId,
RestCredentialName = currentCredentialName,
@@ -2867,8 +2935,8 @@ public partial class DataCoupler : ComponentBase
};
var associationId = await CredentialService.SaveKeyAssociationParallelAsync(association);
Logger.LogDebug("COMPOSITE: Associazione creata con ID: {AssociationId} per record {RecordNumber} (PARALLEL) - Hash: {Hash}",
associationId, recordNumber, finalDataHash);
Logger.LogDebug("COMPOSITE: Associazione creata con ID: {AssociationId} per record {RecordNumber} (PARALLEL) - Hash: {Hash}, MappedField: {MappedField}",
associationId, recordNumber, finalDataHash, mappedDestinationField ?? "N/A");
}
catch (Exception ex)
{