04f0403f12
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
317 lines
14 KiB
Plaintext
317 lines
14 KiB
Plaintext
@page "/test-associations"
|
|
@using CredentialManager.Models
|
|
@using DataConnection.CredentialManagement.Interfaces
|
|
@inject IDataConnectionCredentialService CredentialService
|
|
@inject ILogger<TestAssociations> Logger
|
|
|
|
<PageTitle>Test Associazioni</PageTitle>
|
|
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h3><i class="fas fa-vial"></i> Test Sistema Associazioni Chiave</h3>
|
|
|
|
<div class="row mt-4">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-plus"></i> Crea Associazione Test</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Valore Chiave:</label>
|
|
<input type="text" class="form-control" @bind="testKeyValue" placeholder="es. 12345" />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Entità Destinazione:</label>
|
|
<input type="text" class="form-control" @bind="testDestinationEntity" placeholder="es. BusinessPartners" />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">ID Destinazione:</label>
|
|
<input type="text" class="form-control" @bind="testDestinationId" placeholder="es. BP001" />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Credenziale REST:</label>
|
|
<input type="text" class="form-control" @bind="testRestCredential" placeholder="es. SAP_B1" />
|
|
</div>
|
|
<button class="btn btn-primary" @onclick="CreateTestAssociation">
|
|
<i class="fas fa-save"></i> Crea Associazione
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-search"></i> Cerca Associazione</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Valore Chiave da Cercare:</label>
|
|
<input type="text" class="form-control" @bind="searchKeyValue" placeholder="es. 12345" />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Entità Destinazione:</label>
|
|
<input type="text" class="form-control" @bind="searchDestinationEntity" placeholder="es. BusinessPartners" />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Credenziale REST:</label>
|
|
<input type="text" class="form-control" @bind="searchRestCredential" placeholder="es. SAP_B1" />
|
|
</div>
|
|
<button class="btn btn-info" @onclick="SearchAssociation">
|
|
<i class="fas fa-search"></i> Cerca
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (!string.IsNullOrEmpty(resultMessage))
|
|
{
|
|
<div class="alert @(resultType == "success" ? "alert-success" : resultType == "error" ? "alert-danger" : "alert-info") mt-4">
|
|
<i class="fas @(resultType == "success" ? "fa-check-circle" : resultType == "error" ? "fa-exclamation-triangle" : "fa-info-circle")"></i>
|
|
@resultMessage
|
|
</div>
|
|
}
|
|
|
|
@if (foundAssociation != null)
|
|
{
|
|
<div class="card mt-4">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-link"></i> Associazione Trovata</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<strong>ID:</strong> @foundAssociation.Id<br>
|
|
<strong>Valore Chiave:</strong> @foundAssociation.KeyValue<br>
|
|
<strong>Campo Chiave Sorgente:</strong> @foundAssociation.SourceKeyField<br>
|
|
<strong>Campo Chiave Destinazione:</strong> @foundAssociation.DestinationKeyField<br>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<strong>Entità Destinazione:</strong> @foundAssociation.DestinationEntity<br>
|
|
<strong>ID Destinazione:</strong> @foundAssociation.DestinationId<br>
|
|
<strong>Credenziale REST:</strong> @foundAssociation.RestCredentialName<br>
|
|
<strong>Attiva:</strong> @(foundAssociation.IsActive ? "Sì" : "No")<br>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-2">
|
|
<div class="col-12">
|
|
<strong>Creata:</strong> @foundAssociation.CreatedAt.ToString("dd/MM/yyyy HH:mm:ss")<br>
|
|
@if (foundAssociation.UpdatedAt.HasValue)
|
|
{
|
|
<strong>Aggiornata:</strong> @foundAssociation.UpdatedAt.Value.ToString("dd/MM/yyyy HH:mm:ss")<br>
|
|
}
|
|
@if (foundAssociation.LastVerifiedAt.HasValue)
|
|
{
|
|
<strong>Ultima Verifica:</strong> @foundAssociation.LastVerifiedAt.Value.ToString("dd/MM/yyyy HH:mm:ss")<br>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="card mt-4">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-list"></i> Tutte le Associazioni Attive</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<button class="btn btn-secondary mb-3" @onclick="LoadAllAssociations">
|
|
<i class="fas fa-refresh"></i> Carica Associazioni
|
|
</button>
|
|
|
|
@if (allAssociations != null && allAssociations.Any())
|
|
{
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Valore Chiave</th>
|
|
<th>Entità</th>
|
|
<th>ID Destinazione</th>
|
|
<th>Credenziale</th>
|
|
<th>Creata</th>
|
|
<th>Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var assoc in allAssociations)
|
|
{
|
|
<tr>
|
|
<td>@assoc.Id</td>
|
|
<td>@assoc.KeyValue</td>
|
|
<td>@assoc.DestinationEntity</td>
|
|
<td>@assoc.DestinationId</td>
|
|
<td>@assoc.RestCredentialName</td>
|
|
<td>@assoc.CreatedAt.ToString("dd/MM/yyyy HH:mm")</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-danger" @onclick="() => DeleteAssociation(assoc.Id)">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
else if (allAssociations != null)
|
|
{
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle"></i> Nessuna associazione trovata.
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
private string testKeyValue = "";
|
|
private string testDestinationEntity = "";
|
|
private string testDestinationId = "";
|
|
private string testRestCredential = "";
|
|
|
|
private string searchKeyValue = "";
|
|
private string searchDestinationEntity = "";
|
|
private string searchRestCredential = "";
|
|
|
|
private string resultMessage = "";
|
|
private string resultType = "";
|
|
private KeyAssociation? foundAssociation;
|
|
private List<KeyAssociation>? allAssociations;
|
|
|
|
private async Task CreateTestAssociation()
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(testKeyValue) || string.IsNullOrEmpty(testDestinationEntity) ||
|
|
string.IsNullOrEmpty(testDestinationId) || string.IsNullOrEmpty(testRestCredential))
|
|
{
|
|
resultMessage = "Tutti i campi sono obbligatori.";
|
|
resultType = "error";
|
|
return;
|
|
}
|
|
|
|
var association = new KeyAssociation
|
|
{
|
|
KeyValue = testKeyValue,
|
|
SourceKeyField = "test_field",
|
|
DestinationKeyField = "id",
|
|
DestinationEntity = testDestinationEntity,
|
|
DestinationId = testDestinationId,
|
|
RestCredentialName = testRestCredential,
|
|
CreatedAt = DateTime.UtcNow,
|
|
LastVerifiedAt = DateTime.UtcNow,
|
|
AdditionalInfo = "{\"test\": true}"
|
|
};
|
|
|
|
var id = await CredentialService.SaveKeyAssociationAsync(association);
|
|
resultMessage = $"Associazione creata con successo! ID: {id}";
|
|
resultType = "success";
|
|
|
|
Logger.LogInformation("Associazione test creata: ID={Id}, KeyValue={KeyValue}", id, testKeyValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
resultMessage = $"Errore nella creazione: {ex.Message}";
|
|
resultType = "error";
|
|
Logger.LogError(ex, "Errore nella creazione dell'associazione test");
|
|
}
|
|
}
|
|
|
|
private async Task SearchAssociation()
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(searchKeyValue))
|
|
{
|
|
resultMessage = "Valore chiave obbligatorio per la ricerca.";
|
|
resultType = "error";
|
|
return;
|
|
}
|
|
|
|
foundAssociation = null;
|
|
|
|
if (!string.IsNullOrEmpty(searchDestinationEntity) && !string.IsNullOrEmpty(searchRestCredential))
|
|
{
|
|
foundAssociation = await CredentialService.FindKeyAssociationByValueAsync(
|
|
searchKeyValue, searchDestinationEntity, searchRestCredential);
|
|
}
|
|
else
|
|
{
|
|
foundAssociation = await CredentialService.FindKeyAssociationByValueAsync(searchKeyValue);
|
|
}
|
|
|
|
if (foundAssociation != null)
|
|
{
|
|
resultMessage = "Associazione trovata!";
|
|
resultType = "success";
|
|
Logger.LogInformation("Associazione trovata: ID={Id} per KeyValue={KeyValue}", foundAssociation.Id, searchKeyValue);
|
|
}
|
|
else
|
|
{
|
|
resultMessage = "Nessuna associazione trovata con i criteri specificati.";
|
|
resultType = "info";
|
|
Logger.LogInformation("Nessuna associazione trovata per KeyValue={KeyValue}", searchKeyValue);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
resultMessage = $"Errore nella ricerca: {ex.Message}";
|
|
resultType = "error";
|
|
Logger.LogError(ex, "Errore nella ricerca dell'associazione");
|
|
}
|
|
}
|
|
|
|
private async Task LoadAllAssociations()
|
|
{
|
|
try
|
|
{
|
|
allAssociations = await CredentialService.GetAllActiveKeyAssociationsAsync();
|
|
resultMessage = $"Caricate {allAssociations.Count} associazioni attive.";
|
|
resultType = "info";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
resultMessage = $"Errore nel caricamento: {ex.Message}";
|
|
resultType = "error";
|
|
Logger.LogError(ex, "Errore nel caricamento delle associazioni");
|
|
}
|
|
}
|
|
|
|
private async Task DeleteAssociation(int id)
|
|
{
|
|
try
|
|
{
|
|
var result = await CredentialService.DeleteKeyAssociationAsync(id);
|
|
if (result)
|
|
{
|
|
resultMessage = $"Associazione {id} eliminata con successo.";
|
|
resultType = "success";
|
|
await LoadAllAssociations(); // Ricarica la lista
|
|
}
|
|
else
|
|
{
|
|
resultMessage = $"Errore nell'eliminazione dell'associazione {id}.";
|
|
resultType = "error";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
resultMessage = $"Errore nell'eliminazione: {ex.Message}";
|
|
resultType = "error";
|
|
Logger.LogError(ex, "Errore nell'eliminazione dell'associazione {Id}", id);
|
|
}
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadAllAssociations();
|
|
}
|
|
}
|