diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 84736b8..c0b1c6e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -27,6 +27,22 @@ "options": { "cwd": "${workspaceFolder}/Data_Coupler" } + }, + { + "label": "Publish Data_Coupler", + "type": "shell", + "command": "dotnet", + "args": [ + "publish", + "--configuration", + "Release", + "--output", + "${workspaceFolder}/publish", + "--project", + "Data_Coupler/Data_Coupler.csproj" + ], + "group": "build", + "problemMatcher": [] } ] } \ No newline at end of file diff --git a/DataConnection/REST/Implementations/SalesforceServiceClient.cs b/DataConnection/REST/Implementations/SalesforceServiceClient.cs index a7b4fd2..dd3308e 100644 --- a/DataConnection/REST/Implementations/SalesforceServiceClient.cs +++ b/DataConnection/REST/Implementations/SalesforceServiceClient.cs @@ -23,6 +23,17 @@ namespace DataConnection.REST.Implementations private string? _instanceUrl; private DateTime _tokenExpiry; + /// + /// Configurazione JSON per garantire la compatibilità con Salesforce API + /// Utilizza sempre la cultura invariante per i numeri per evitare problemi con virgole/punti decimali + /// + private static readonly JsonSerializerOptions SalesforceJsonOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + public SalesforceServiceClient(HttpClient httpClient, RestServiceOptions options) : base(httpClient, options) { @@ -392,9 +403,19 @@ namespace DataConnection.REST.Implementations Console.WriteLine($"--- Salesforce Entity Creation Attempt ---"); Console.WriteLine($"SObject: {entityName}"); Console.WriteLine($"Target URL: {createUri}"); - Console.WriteLine($"Data: {System.Text.Json.JsonSerializer.Serialize(entityData)}"); + Console.WriteLine($"Data: {JsonSerializer.Serialize(entityData, SalesforceJsonOptions)}"); - var response = await _httpClient.PostAsJsonAsync(createUri, entityData, cancellationToken); + // Normalizza i valori numerici per evitare problemi con virgole decimali + var normalizedData = NormalizeNumericValues(entityData); + + // Usa StringContent con configurazione JSON specifica per Salesforce + var jsonContent = new StringContent( + JsonSerializer.Serialize(normalizedData, SalesforceJsonOptions), + System.Text.Encoding.UTF8, + "application/json" + ); + + var response = await _httpClient.PostAsync(createUri, jsonContent, cancellationToken); if (!response.IsSuccessStatusCode) { @@ -414,7 +435,7 @@ namespace DataConnection.REST.Implementations return entityData; // Return original data if no response content // Salesforce returns creation result with Id and success status - var creationResult = System.Text.Json.JsonSerializer.Deserialize>(responseContent); + var creationResult = JsonSerializer.Deserialize>(responseContent, SalesforceJsonOptions); // Merge the original data with the creation result (which includes the new Id) if (creationResult != null) @@ -653,6 +674,76 @@ namespace DataConnection.REST.Implementations return await AuthenticateAsync(cancellationToken); } + /// + /// Normalizza i valori numerici in un dictionary per garantire compatibilità con Salesforce API + /// Converte valori decimali con virgola in formato con punto decimale + /// + private static Dictionary NormalizeNumericValues(Dictionary data) + { + var normalizedData = new Dictionary(); + bool hasNormalized = false; + + foreach (var kvp in data) + { + var value = kvp.Value; + + if (value != null) + { + // Se è una stringa che rappresenta un numero decimale con virgola, convertila + if (value is string stringValue && IsNumericWithComma(stringValue)) + { + if (decimal.TryParse(stringValue, System.Globalization.NumberStyles.Number, + System.Globalization.CultureInfo.CurrentCulture, out decimal decimalValue)) + { + // Converte in double usando cultura invariante per garantire punto decimale + var normalizedValue = double.Parse(decimalValue.ToString(System.Globalization.CultureInfo.InvariantCulture)); + normalizedData[kvp.Key] = normalizedValue; + Console.WriteLine($"NUMERIC NORMALIZATION: {kvp.Key}: '{stringValue}' → {normalizedValue}"); + hasNormalized = true; + } + else + { + normalizedData[kvp.Key] = value; + } + } + // Se è già un decimal, convertilo in double con cultura invariante + else if (value is decimal dec) + { + var normalizedValue = double.Parse(dec.ToString(System.Globalization.CultureInfo.InvariantCulture)); + normalizedData[kvp.Key] = normalizedValue; + Console.WriteLine($"DECIMAL NORMALIZATION: {kvp.Key}: {dec} → {normalizedValue}"); + hasNormalized = true; + } + else + { + normalizedData[kvp.Key] = value; + } + } + else + { + normalizedData[kvp.Key] = value!; + } + } + + if (hasNormalized) + { + Console.WriteLine($"NORMALIZATION SUMMARY: Processed {data.Count} fields, normalized {normalizedData.Count(kvp => kvp.Value is double)} numeric values"); + } + + return normalizedData; + } + + /// + /// Verifica se una stringa rappresenta un numero con virgola decimale + /// + private static bool IsNumericWithComma(string value) + { + if (string.IsNullOrEmpty(value)) return false; + + // Pattern per numeri con virgola: opzionale segno, cifre, virgola, cifre + return System.Text.RegularExpressions.Regex.IsMatch(value.Trim(), @"^[+-]?\d+,\d+$"); + } + // --- Nested classes for deserializing Salesforce responses --- private class SalesforceTokenResponse { @@ -928,17 +1019,27 @@ namespace DataConnection.REST.Implementations for (int i = 0; i < batch.Count; i++) { + // Normalizza i valori numerici per evitare problemi con virgole decimali + var normalizedData = NormalizeNumericValues(batch[i]); + var subrequest = new SalesforceCompositeSubRequest { Method = "POST", Url = $"/services/data/v60.0/sobjects/{entityName}/", ReferenceId = $"create_{startIndex + i}", - Body = batch[i] + Body = normalizedData }; compositeRequest.CompositeRequest.Add(subrequest); } - var response = await _httpClient.PostAsJsonAsync(compositeUri, compositeRequest, cancellationToken); + // Usa StringContent con configurazione JSON specifica per Salesforce + var jsonContent = new StringContent( + JsonSerializer.Serialize(compositeRequest, SalesforceJsonOptions), + System.Text.Encoding.UTF8, + "application/json" + ); + + var response = await _httpClient.PostAsync(compositeUri, jsonContent, cancellationToken); if (!response.IsSuccessStatusCode) { @@ -956,7 +1057,7 @@ namespace DataConnection.REST.Implementations } var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - var compositeResponse = JsonSerializer.Deserialize(responseContent); + var compositeResponse = JsonSerializer.Deserialize(responseContent, SalesforceJsonOptions); var results = new List(); @@ -975,7 +1076,7 @@ namespace DataConnection.REST.Implementations { if (subResponse.Body is JsonElement bodyElement) { - var bodyDict = JsonSerializer.Deserialize>(bodyElement.GetRawText()); + var bodyDict = JsonSerializer.Deserialize>(bodyElement.GetRawText(), SalesforceJsonOptions); result.CreatedData = bodyDict; // Extract the created ID @@ -1082,18 +1183,28 @@ namespace DataConnection.REST.Implementations var entityId = kvp.Key; var entityData = kvp.Value; + // Normalizza i valori numerici per evitare problemi con virgole decimali + var normalizedData = NormalizeNumericValues(entityData); + var subrequest = new SalesforceCompositeSubRequest { Method = "PATCH", Url = $"/services/data/v60.0/sobjects/{entityName}/{entityId}", ReferenceId = $"update_{startIndex + index}", - Body = entityData + Body = normalizedData }; compositeRequest.CompositeRequest.Add(subrequest); index++; } - var response = await _httpClient.PostAsJsonAsync(compositeUri, compositeRequest, cancellationToken); + // Usa StringContent con configurazione JSON specifica per Salesforce + var jsonContent = new StringContent( + JsonSerializer.Serialize(compositeRequest, SalesforceJsonOptions), + System.Text.Encoding.UTF8, + "application/json" + ); + + var response = await _httpClient.PostAsync(compositeUri, jsonContent, cancellationToken); if (!response.IsSuccessStatusCode) { @@ -1112,7 +1223,7 @@ namespace DataConnection.REST.Implementations } var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - var compositeResponse = JsonSerializer.Deserialize(responseContent); + var compositeResponse = JsonSerializer.Deserialize(responseContent, SalesforceJsonOptions); var results = new List(); diff --git a/Data_Coupler/Pages/DataCoupler.razor.cs b/Data_Coupler/Pages/DataCoupler.razor.cs index f555451..dc20a3d 100644 --- a/Data_Coupler/Pages/DataCoupler.razor.cs +++ b/Data_Coupler/Pages/DataCoupler.razor.cs @@ -1627,7 +1627,12 @@ public partial class DataCoupler : ComponentBase case "edm.decimal": case "edm.double": - if (decimal.TryParse(value.ToString(), out decimal decVal)) + // Usa InvariantCulture per garantire che i decimali usino il punto come separatore + if (decimal.TryParse(value.ToString(), System.Globalization.NumberStyles.Number, + System.Globalization.CultureInfo.InvariantCulture, out decimal decVal)) + return decVal; + // Fallback: prova con la cultura corrente nel caso il valore usi la virgola + if (decimal.TryParse(value.ToString(), out decVal)) return decVal; break; diff --git a/Data_Coupler/wwwroot/data/credentials.db-shm b/Data_Coupler/wwwroot/data/credentials.db-shm index c8d0cbb..fe9ac28 100644 Binary files a/Data_Coupler/wwwroot/data/credentials.db-shm and b/Data_Coupler/wwwroot/data/credentials.db-shm differ diff --git a/SALESFORCE_JSON_SERIALIZATION_FIX.md b/SALESFORCE_JSON_SERIALIZATION_FIX.md new file mode 100644 index 0000000..e69de29