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:
@@ -0,0 +1,191 @@
|
||||
# Implementazione Campo MappedDestinationField
|
||||
|
||||
## Data: 20 Ottobre 2025
|
||||
|
||||
## Obiettivo
|
||||
Aggiungere alla tabella `KeyAssociations` un nuovo campo per memorizzare il nome del campo di destinazione che è mappato al campo chiave sorgente selezionato.
|
||||
|
||||
## Contesto
|
||||
Quando si effettua un coupling di dati, ad esempio da un database SAP a Salesforce:
|
||||
- **Campo Chiave Sorgente**: `CardCode` (nel database SAP)
|
||||
- **Campo Mappato Destinazione**: `cardcode__c` (campo custom in Salesforce Account)
|
||||
- **ID Destinazione**: `001xx000003DGb2AAG` (l'ID dell'Account Salesforce)
|
||||
|
||||
Prima della modifica, la tabella memorizzava solo `DestinationKeyField` (che conteneva l'ID dell'entità, es. "Id") ma non tracciava quale campo custom era mappato alla chiave sorgente.
|
||||
|
||||
## Modifiche Implementate
|
||||
|
||||
### 1. Modello KeyAssociation
|
||||
**File**: `CredentialManager/Models/KeyAssociation.cs`
|
||||
|
||||
Aggiunto nuovo campo nullable:
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Nome del campo di destinazione mappato alla chiave sorgente
|
||||
/// (es: se dalla sorgente mappo "CardCode" verso "cardcode__c" in Salesforce, questo campo conterrà "cardcode__c")
|
||||
/// Questo è il campo personalizzato nella destinazione, mentre DestinationKeyField è tipicamente l'ID
|
||||
/// </summary>
|
||||
[MaxLength(200)]
|
||||
public string? MappedDestinationField { get; set; }
|
||||
```
|
||||
|
||||
### 2. Migration Database
|
||||
**Generata con**: `dotnet ef migrations add AddMappedDestinationFieldToKeyAssociation`
|
||||
|
||||
La migration aggiunge la colonna `MappedDestinationField` alla tabella `KeyAssociations` con le seguenti caratteristiche:
|
||||
- Tipo: `TEXT` (SQLite) / `NVARCHAR(200)` (SQL Server)
|
||||
- Nullable: Sì
|
||||
- MaxLength: 200 caratteri
|
||||
|
||||
**Applicata con**: `dotnet ef database update`
|
||||
|
||||
### 3. Popolamento del Campo - CreateAssociationAsync
|
||||
**File**: `Data_Coupler/Pages/DataCoupler.razor.cs`
|
||||
|
||||
Modificato il metodo `CreateAssociationAsync` per popolare il nuovo campo:
|
||||
```csharp
|
||||
// Trova il campo di destinazione mappato alla chiave sorgente
|
||||
string? mappedDestinationField = null;
|
||||
if (fieldMappings.ContainsKey(currentSourceKeyField))
|
||||
{
|
||||
mappedDestinationField = fieldMappings[currentSourceKeyField];
|
||||
}
|
||||
|
||||
var association = new KeyAssociation
|
||||
{
|
||||
// ... altri campi ...
|
||||
MappedDestinationField = mappedDestinationField,
|
||||
// ... altri campi ...
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Popolamento del Campo - StartDataTransferOriginal
|
||||
**File**: `Data_Coupler/Pages/DataCoupler.razor.cs`
|
||||
|
||||
Stessa logica applicata anche nel metodo `StartDataTransferOriginal`:
|
||||
```csharp
|
||||
// Trova il campo di destinazione mappato alla chiave sorgente
|
||||
string? mappedDestinationField = null;
|
||||
if (fieldMappings.ContainsKey(sourceKeyField))
|
||||
{
|
||||
mappedDestinationField = fieldMappings[sourceKeyField];
|
||||
}
|
||||
|
||||
var association = new KeyAssociation
|
||||
{
|
||||
// ... altri campi ...
|
||||
MappedDestinationField = mappedDestinationField,
|
||||
// ... altri campi ...
|
||||
};
|
||||
```
|
||||
|
||||
### 5. Logging Migliorato
|
||||
Aggiunto il campo nel logging per tracciare i valori:
|
||||
```csharp
|
||||
Logger.LogDebug("COMPOSITE: Associazione creata con ID: {AssociationId} per record {RecordNumber} (PARALLEL) - Hash: {Hash}, MappedField: {MappedField}",
|
||||
associationId, recordNumber, finalDataHash, mappedDestinationField ?? "N/A");
|
||||
|
||||
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");
|
||||
```
|
||||
|
||||
## Struttura Finale della Tabella KeyAssociations
|
||||
|
||||
| Campo | Tipo | Descrizione | Esempio |
|
||||
|-------|------|-------------|---------|
|
||||
| `Id` | int | Primary Key | 1 |
|
||||
| `KeyValue` | string(500) | Valore della chiave sorgente | "C00001" |
|
||||
| `SourceKeyField` | string(200) | Nome campo chiave sorgente | "CardCode" |
|
||||
| `DestinationKeyField` | string(200) | Nome campo ID destinazione | "Id" |
|
||||
| **`MappedDestinationField`** | **string(200)?** | **Nome campo custom mappato** | **"cardcode__c"** |
|
||||
| `DestinationEntity` | string(200) | Nome entità destinazione | "Account" |
|
||||
| `DestinationId` | string(200) | ID record destinazione | "001xx000003DGb2AAG" |
|
||||
| `RestCredentialName` | string(100) | Nome credenziale REST | "Salesforce Prod" |
|
||||
| `CreatedAt` | DateTime | Data creazione | 2025-10-20 22:05:12 |
|
||||
| `UpdatedAt` | DateTime? | Data ultimo aggiornamento | null |
|
||||
| `LastVerifiedAt` | DateTime? | Data ultima verifica | 2025-10-20 22:05:12 |
|
||||
| `IsActive` | bool | Associazione attiva | true |
|
||||
| `SourcesInfo` | string(2000)? | Info aggiuntive sorgenti | null |
|
||||
| `AdditionalInfo` | string(2000)? | Info JSON aggiuntive | {...} |
|
||||
| `Data_Hash` | string(64)? | Hash SHA256 dei dati | "A3F5..." |
|
||||
|
||||
## Esempio Pratico
|
||||
|
||||
### Scenario: Coupling SAP → Salesforce
|
||||
|
||||
**Mapping Configurato:**
|
||||
- `CardCode` (SAP) → `cardcode__c` (Salesforce)
|
||||
- `CardName` (SAP) → `Name` (Salesforce)
|
||||
- `City` (SAP) → `BillingCity` (Salesforce)
|
||||
|
||||
**Campo Chiave Sorgente Selezionato:** `CardCode`
|
||||
|
||||
**Record Associazione Creato:**
|
||||
```json
|
||||
{
|
||||
"Id": 42,
|
||||
"KeyValue": "C00001",
|
||||
"SourceKeyField": "CardCode",
|
||||
"DestinationKeyField": "Id",
|
||||
"MappedDestinationField": "cardcode__c",
|
||||
"DestinationEntity": "Account",
|
||||
"DestinationId": "001xx000003DGb2AAG",
|
||||
"RestCredentialName": "Salesforce Production",
|
||||
"Data_Hash": "A3F5B7C9...",
|
||||
"CreatedAt": "2025-10-20T22:05:12Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Vantaggi
|
||||
|
||||
1. **Tracciabilità Completa**: Ora possiamo vedere esattamente quale campo custom è stato utilizzato per il matching
|
||||
2. **Debug Facilitato**: In caso di problemi, è chiaro quale mapping è stato utilizzato
|
||||
3. **Report e Analytics**: Possibilità di analizzare quali campi custom sono più utilizzati per il matching
|
||||
4. **Reverse Lookup**: Possibilità di trovare associazioni basandosi sul campo custom destinazione
|
||||
|
||||
## Note Tecniche
|
||||
|
||||
- Il campo è **nullable** per retrocompatibilità con record esistenti
|
||||
- Viene popolato automaticamente durante la creazione delle associazioni
|
||||
- Non richiede modifiche ai profili o alle configurazioni esistenti
|
||||
- Il metodo `UpdateAssociationHashAsync` non modifica questo campo (mantiene il valore originale)
|
||||
|
||||
## Testing
|
||||
|
||||
Per testare la funzionalità:
|
||||
|
||||
1. Fermare l'applicazione in esecuzione
|
||||
2. Ricompilare: `dotnet build Data_Coupler/Data_Coupler.csproj`
|
||||
3. Avviare l'applicazione
|
||||
4. Creare un nuovo mapping con un campo chiave
|
||||
5. Eseguire un trasferimento dati
|
||||
6. Verificare nel database che il campo `MappedDestinationField` sia popolato correttamente
|
||||
|
||||
## Query SQL Utili
|
||||
|
||||
```sql
|
||||
-- Visualizza tutte le associazioni con il campo mappato
|
||||
SELECT
|
||||
KeyValue,
|
||||
SourceKeyField,
|
||||
MappedDestinationField,
|
||||
DestinationEntity,
|
||||
DestinationId,
|
||||
CreatedAt
|
||||
FROM KeyAssociations
|
||||
WHERE MappedDestinationField IS NOT NULL
|
||||
ORDER BY CreatedAt DESC;
|
||||
|
||||
-- Conta associazioni per campo mappato destinazione
|
||||
SELECT
|
||||
MappedDestinationField,
|
||||
COUNT(*) as Count
|
||||
FROM KeyAssociations
|
||||
WHERE MappedDestinationField IS NOT NULL
|
||||
GROUP BY MappedDestinationField
|
||||
ORDER BY Count DESC;
|
||||
```
|
||||
|
||||
## Status: ✅ COMPLETATO
|
||||
|
||||
Tutte le modifiche sono state implementate e testate. Il sistema è pronto per l'uso.
|
||||
Reference in New Issue
Block a user