feat: Implementato sistema di associazioni chiave per prevenire duplicati nel data coupling

BREAKING CHANGE: Rimosso completamente il vecchio sistema RecordAssociation

Modifiche principali:
- Sostituito RecordAssociation con KeyAssociation basato sui valori delle chiavi
- Implementata logica robusta di UPDATE vs INSERT basata su associazioni esistenti
- Aggiunta normalizzazione delle chiavi (.Trim()) per consistenza
- Implementato fallback nella ricerca associazioni per maggiore affidabilità
- Sostituita verifica pre-UPDATE con tentativo diretto più efficiente

Componenti modificati:
- Nuovo modello: KeyAssociation.cs con campi ottimizzati
- Nuovo servizio: KeyAssociationService.cs con metodi completi
- Aggiornato: DataCoupler.razor con logica migliorata di gestione associazioni
- Aggiornato: CredentialDbContext per gestire solo KeyAssociations
- Aggiornati: tutti i servizi di interfaccia per supportare il nuovo sistema
- Creata: pagina KeyAssociations.razor per gestione associazioni
- Aggiornato: NavMenu.razor con link alla gestione associazioni

Miglioramenti tecnici:
- Logica di UPDATE più robusta: tenta direttamente l'aggiornamento invece di verificare prima l'esistenza
- Gestione errori migliorata con cleanup automatico delle associazioni non valide
- Debug logging estensivo per troubleshooting
- Fallback nella ricerca associazioni se parametri specifici falliscono
- Normalizzazione valori chiave per prevenire problemi di whitespace

Risultato:
Il sistema ora previene correttamente i duplicati utilizzando le associazioni per decidere
se fare INSERT (nuovo record) o UPDATE (record esistente) basandosi sui valori delle chiavi.

Database:
- Creata migrazione EF per rimuovere RecordAssociations e aggiungere KeyAssociations
- Eliminati file e codice legacy non più necessari
This commit is contained in:
2025-06-29 20:44:20 +02:00
parent 2238ddc4bf
commit 04f0403f12
23 changed files with 2051 additions and 1161 deletions
@@ -3,33 +3,38 @@ using System.ComponentModel.DataAnnotations;
namespace CredentialManager.Models;
/// <summary>
/// Entità per memorizzare le associazioni tra record sorgente e destinazione
/// Entità per memorizzare le associazioni basate sui valori delle chiavi
/// Un'associazione lega un valore di chiave a un record di destinazione,
/// indipendentemente dalla sorgente che ha generato quel valore
/// </summary>
public class RecordAssociation
public class KeyAssociation
{
[Key]
public int Id { get; set; }
/// <summary>
/// Nome della sorgente dati (nome tabella/file/foglio)
/// </summary>
[Required]
[MaxLength(200)]
public string SourceName { get; set; } = string.Empty;
/// <summary>
/// Tipo di sorgente (database, file)
/// </summary>
[Required]
[MaxLength(50)]
public string SourceType { get; set; } = string.Empty;
/// <summary>
/// Chiave del record sorgente (può essere un ID o una combinazione di campi)
/// Valore della chiave che identifica univocamente l'oggetto business
/// (es: "CUST001", "12345", "ABC-DEF-GHI")
/// </summary>
[Required]
[MaxLength(500)]
public string SourceKey { get; set; } = string.Empty;
public string KeyValue { get; set; } = string.Empty;
/// <summary>
/// Nome del campo chiave nella sorgente
/// (es: "CustomerCode", "ID", "ArticleNumber")
/// </summary>
[Required]
[MaxLength(200)]
public string SourceKeyField { get; set; } = string.Empty;
/// <summary>
/// Nome del campo chiave nella destinazione
/// (es: "CardCode", "DocEntry", "ItemCode")
/// </summary>
[Required]
[MaxLength(200)]
public string DestinationKeyField { get; set; } = string.Empty;
/// <summary>
/// Nome dell'entità di destinazione
@@ -46,7 +51,7 @@ public class RecordAssociation
public string DestinationId { get; set; } = string.Empty;
/// <summary>
/// Nome della credenziale REST utilizzata
/// Nome della credenziale REST utilizzata per la destinazione
/// </summary>
[Required]
[MaxLength(100)]
@@ -62,11 +67,22 @@ public class RecordAssociation
/// </summary>
public DateTime? UpdatedAt { get; set; }
/// <summary>
/// Data e ora dell'ultima verifica che il record di destinazione esiste ancora
/// </summary>
public DateTime? LastVerifiedAt { get; set; }
/// <summary>
/// Indica se l'associazione è ancora attiva
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// Informazioni aggiuntive sui record che hanno contribuito a questa associazione
/// </summary>
[MaxLength(2000)]
public string? SourcesInfo { get; set; }
/// <summary>
/// Informazioni aggiuntive in formato JSON
/// </summary>