Files
Data-Coupler/PRE_DISCOVERY_FORCED_UPDATE.md
Alessio 39d7124ce1 feat: Implementato sistema Pre-Discovery con supporto esecuzioni schedulate
- Fix FindEntitiesByKeysAsync: SOQL query universale al posto di External ID GET
  * Disabilitato External ID GET (funziona solo per campi marcati External ID)
  * Query SOQL come metodo primario funzionante per tutti i campi
  * Logging dettagliato per diagnostica ricerche Salesforce

- Pre-Discovery nei trasferimenti manuali (DataCoupler.razor.cs)
  * Ricerca automatica nella destinazione quando KeyAssociation non esiste
  * Creazione associazione con marker CreatedBy="PreDiscovery"
  * Aggiornamento forzato senza controllo hash per associazioni Pre-Discovery
  * Supporto parallel processing thread-safe

- Pre-Discovery nei trasferimenti schedulati (ScheduledProfileExecutionService.cs)
  * Logica identica a trasferimenti manuali
  * Marker aggiuntivo ScheduledTransfer=true per tracciabilità
  * Aggiornamento forzato per prima sincronizzazione

- Sistema aggiornamento forzato
  * Verifica AdditionalInfo per identificare associazioni Pre-Discovery
  * Skip controllo hash per associazioni appena scoperte
  * Garantisce sincronizzazione dati al primo trasferimento

Vantaggi:
- Prevenzione automatica duplicati in Salesforce
- Recupero record esistenti senza associazioni
- Parità funzionale tra esecuzioni manuali e schedulate
- Performance ottimizzate con controllo hash per esecuzioni successive

Docs: PRE_DISCOVERY_SYSTEM.md, PRE_DISCOVERY_FORCED_UPDATE.md, SALESFORCE_FIND_ENTITIES_FIX.md
2025-10-21 00:48:03 +02:00

11 KiB

Pre-Discovery: Aggiornamento Forzato

📋 Requisito Implementato

Quando il Pre-Discovery trova un record esistente nella destinazione e crea una nuova associazione, il sistema deve forzare l'aggiornamento del record saltando il controllo hash.

🎯 Rationale

Quando un'associazione viene creata dal Pre-Discovery significa che:

  1. Il record esiste nella destinazione Salesforce
  2. Non esisteva un'associazione in KeyAssociations
  3. 🔍 I dati potrebbero essere diversi tra sorgente e destinazione
  4. 🔄 Vogliamo sincronizzare i dati al primo trasferimento

Quindi ha senso forzare l'aggiornamento senza controllare l'hash, per assicurarci che i dati siano allineati.

🔧 Implementazione

Identificazione Associazioni Pre-Discovery

Le associazioni create dal Pre-Discovery hanno un marker nel campo AdditionalInfo:

{
  "CreatedBy": "PreDiscovery",
  "DiscoveredAt": "2025-10-21T10:30:00Z",
  "MappingCount": 5
}

Logica di Controllo

Il sistema verifica il campo AdditionalInfo per determinare se l'associazione proviene dal Pre-Discovery:

// Verifica se l'associazione è stata creata dal Pre-Discovery
var isPreDiscoveryAssociation = false;
if (!string.IsNullOrEmpty(existingAssociation.AdditionalInfo))
{
    try
    {
        var additionalInfo = JsonSerializer.Deserialize<Dictionary<string, object>>(existingAssociation.AdditionalInfo);
        if (additionalInfo != null && additionalInfo.ContainsKey("CreatedBy"))
        {
            var createdBy = additionalInfo["CreatedBy"]?.ToString();
            isPreDiscoveryAssociation = createdBy == "PreDiscovery";
        }
    }
    catch
    {
        // Ignora errori di parsing
    }
}

Flusso di Decisione

┌─────────────────────────────────────┐
│ Esiste associazione attiva?         │
└─────────────┬───────────────────────┘
              │
              ├─ NO ──> Crea nuovo record
              │
              └─ SI ──> È da Pre-Discovery?
                        │
                        ├─ SI ──> ✅ FORZA AGGIORNAMENTO (skip hash)
                        │
                        └─ NO ──> Controlla hash
                                  │
                                  ├─ Hash identico ──> Skip record
                                  │
                                  └─ Hash diverso ──> Aggiorna record

Codice Modificato

File: Data_Coupler/Pages/DataCoupler.razor.cs
Metodo: StartDataTransferWithComposite() (linea ~2840-2890)

if (existingAssociation != null && existingAssociation.IsActive)
{
    // Verifica se l'associazione è stata creata dal Pre-Discovery
    var isPreDiscoveryAssociation = false;
    if (!string.IsNullOrEmpty(existingAssociation.AdditionalInfo))
    {
        try
        {
            var additionalInfo = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(existingAssociation.AdditionalInfo);
            if (additionalInfo != null && additionalInfo.ContainsKey("CreatedBy"))
            {
                var createdBy = additionalInfo["CreatedBy"]?.ToString();
                isPreDiscoveryAssociation = createdBy == "PreDiscovery";
            }
        }
        catch
        {
            // Ignora errori di parsing
        }
    }

    // 🔍 PRE-DISCOVERY: Se l'associazione è stata appena creata dal Pre-Discovery, FORZA l'aggiornamento
    if (isPreDiscoveryAssociation)
    {
        // Forza aggiornamento senza controllo hash
        recordsForUpdate.Add((restData, existingAssociation.DestinationId, record, recordNumber, currentDataHash));
        Logger.LogInformation("COMPOSITE PARALLEL: Record {RecordNumber} marcato per AGGIORNAMENTO FORZATO (Pre-Discovery) - EntityId: {EntityId}",
            recordNumber, existingAssociation.DestinationId);
    }
    else
    {
        // CONTROLLO HASH: Verifica se i dati sono cambiati (solo per associazioni esistenti)
        var existingHash = existingAssociation.Data_Hash;
        
        if (!string.IsNullOrEmpty(existingHash) && existingHash.Equals(currentDataHash, StringComparison.OrdinalIgnoreCase))
        {
            // I dati non sono cambiati, salta questo record
            recordsSkipped.Add((record, recordNumber, "Dati non modificati (hash identico)"));
            Logger.LogDebug("COMPOSITE PARALLEL: Record {RecordNumber} saltato - hash identico: {Hash}",
                recordNumber, currentDataHash);
        }
        else
        {
            // I dati sono cambiati o l'hash è vuoto, procedi con l'aggiornamento
            recordsForUpdate.Add((restData, existingAssociation.DestinationId, record, recordNumber, currentDataHash));
            Logger.LogDebug("COMPOSITE PARALLEL: Record {RecordNumber} marcato per aggiornamento (EntityId: {EntityId}) - hash diverso: old={OldHash}, new={NewHash}",
                recordNumber, existingAssociation.DestinationId, existingHash ?? "NULL", currentDataHash);
        }
    }
}

📊 Logging

Il sistema ora produce log specifici per aggiornamenti forzati:

COMPOSITE PARALLEL: Record 42 marcato per AGGIORNAMENTO FORZATO (Pre-Discovery) - EntityId: 003xxxxxxxxxxxxx

vs. aggiornamenti normali:

COMPOSITE PARALLEL: Record 42 marcato per aggiornamento (EntityId: 003xxxxxxxxxxxxx) - hash diverso: old=abc123, new=def456

🎯 Comportamento Atteso

Scenario 1: Prima Esecuzione con Pre-Discovery

Setup:

  • Tabella KeyAssociations vuota
  • Record già presenti in Salesforce

Risultato:

  1. Pre-Discovery trova i record esistenti
  2. Crea associazioni con CreatedBy: "PreDiscovery"
  3. Forza l'aggiornamento di tutti i record trovati (ignora hash)
  4. Aggiorna Data_Hash con il nuovo valore
  5. Record sincronizzati tra sorgente e destinazione

Scenario 2: Seconda Esecuzione (Associazioni Esistenti)

Setup:

  • Tabella KeyAssociations popolata (dalla prima esecuzione)
  • Record già sincronizzati

Risultato:

  1. Trova associazioni esistenti (NON da Pre-Discovery)
  2. Controlla l'hash dei dati
  3. Se hash identico → Skip record (performance ottimizzata)
  4. Se hash diverso → Aggiorna record normalmente

Scenario 3: Record Modificato in Sorgente

Setup:

  • Associazione esistente
  • 🔄 Dati modificati in sorgente

Risultato:

  1. Trova associazione esistente
  2. Calcola nuovo hash: new_hash ≠ old_hash
  3. Aggiorna il record in destinazione
  4. Salva il nuovo hash

🔄 Ciclo di Vita Associazione Pre-Discovery

┌──────────────────────────────────────────────────────────────┐
│ 1° TRASFERIMENTO (KeyAssociations vuota)                     │
├──────────────────────────────────────────────────────────────┤
│ Pre-Discovery: Cerca record in Salesforce                    │
│ ✅ Trovato → Crea associazione con CreatedBy="PreDiscovery"  │
│ 🔄 AGGIORNAMENTO FORZATO (skip hash check)                   │
│ 💾 Salva nuovo hash                                          │
└──────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────┐
│ 2° TRASFERIMENTO (Associazione esistente)                    │
├──────────────────────────────────────────────────────────────┤
│ Trova associazione (CreatedBy="PreDiscovery")                │
│ 🔍 CONTROLLO HASH                                             │
│   • Hash identico → ⏭️ Skip (performance)                     │
│   • Hash diverso → 🔄 Aggiorna normalmente                    │
└──────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────┐
│ TRASFERIMENTI SUCCESSIVI                                      │
├──────────────────────────────────────────────────────────────┤
│ Comportamento normale con controllo hash                     │
│ (l'associazione non è più "nuova" dal Pre-Discovery)         │
└──────────────────────────────────────────────────────────────┘

⚠️ Note Importanti

Metodo Original (Non-Composite)

Il metodo StartDataTransferOriginal() non ha controllo hash. Tenta sempre l'aggiornamento direttamente, quindi non necessita di modifiche per il Pre-Discovery.

Performance

  • Prima esecuzione: Tutti i record trovati dal Pre-Discovery vengono aggiornati forzatamente
  • Esecuzioni successive: Controllo hash ottimizza le performance saltando record non modificati

Pulizia Marker

Il marker CreatedBy: "PreDiscovery" rimane nell'associazione per tutta la sua vita. Questo permette di:

  1. Tracciare l'origine dell'associazione
  2. Debugging più semplice
  3. Statistiche su quante associazioni sono state scoperte automaticamente

🧪 Test Case

Test 1: Pre-Discovery con Aggiornamento Forzato

SETUP:
  - KeyAssociations: vuota
  - Salesforce: Record con Email="test@example.com"
  - Sorgente: Record con Email="test@example.com" + dati aggiornati

EXPECTED:
  1. Pre-Discovery trova record in Salesforce
  2. Crea associazione con CreatedBy="PreDiscovery"
  3. FORZA aggiornamento (salta controllo hash)
  4. Log: "AGGIORNAMENTO FORZATO (Pre-Discovery)"
  5. Record aggiornato in Salesforce

Test 2: Secondo Trasferimento con Hash Check

SETUP:
  - KeyAssociations: popolata (da Test 1)
  - Sorgente: Dati NON modificati

EXPECTED:
  1. Trova associazione esistente
  2. Calcola hash: identico
  3. Skip record (performance)
  4. Log: "saltato - hash identico"
  5. Nessuna chiamata API a Salesforce

Data Implementazione: 21 Ottobre 2025
File Modificato: Data_Coupler/Pages/DataCoupler.razor.cs
Metodo: StartDataTransferWithComposite()
Linee: ~2840-2890