1198 lines
47 KiB
C#
1198 lines
47 KiB
C#
using GestionaDenuncias.Shared.Models;
|
|
using Microsoft.Extensions.Options;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace ApiDenuncias.Services
|
|
{
|
|
public class GestionaService : IGestionaService
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly GestionaOptions _opts;
|
|
|
|
public GestionaService(HttpClient http, IOptions<GestionaOptions> optsAccessor)
|
|
{
|
|
_http = http;
|
|
_opts = optsAccessor.Value;
|
|
}
|
|
|
|
// =========================================================
|
|
// Helpers
|
|
// =========================================================
|
|
|
|
private static string BuildFilterViewParam(object filterView)
|
|
{
|
|
var json = JsonSerializer.Serialize(filterView);
|
|
var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
|
|
return $"filter-view={Uri.EscapeDataString(b64)}";
|
|
}
|
|
|
|
private static string? GetLinkHref(JsonElement item, string rel)
|
|
{
|
|
if (!item.TryGetProperty("links", out var links) || links.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (var link in links.EnumerateArray())
|
|
{
|
|
if (link.TryGetProperty("rel", out var relProp) &&
|
|
string.Equals(relProp.GetString(), rel, StringComparison.OrdinalIgnoreCase) &&
|
|
link.TryGetProperty("href", out var hrefProp) &&
|
|
hrefProp.ValueKind == JsonValueKind.String)
|
|
{
|
|
return hrefProp.GetString();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Reemplaza este helper si quieres controlar la versión en Accept:
|
|
private void AddTokenAndAccept(HttpRequestMessage req, string mediaType, string? version = null)
|
|
{
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.Accept.Clear();
|
|
if (string.IsNullOrWhiteSpace(version))
|
|
{
|
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
|
|
}
|
|
else
|
|
{
|
|
var mt = new MediaTypeWithQualityHeaderValue(mediaType);
|
|
mt.Parameters.Add(new NameValueHeaderValue("version", version));
|
|
req.Headers.Accept.Add(mt);
|
|
}
|
|
}
|
|
|
|
// Helper estilo Postman para /rest/files sin filter-view
|
|
private void AddBasicHeaders(HttpRequestMessage req)
|
|
{
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.Accept.Clear();
|
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
}
|
|
|
|
|
|
|
|
// =========================================================
|
|
// Expedientes (file)
|
|
// =========================================================
|
|
|
|
public async Task<GestionaCreateFileResponse> CreateFileAsync(Guid procedureId, string subject, string documentSeries, string siaCode)
|
|
{
|
|
_ = subject;
|
|
_ = documentSeries;
|
|
_ = siaCode;
|
|
|
|
var effectiveProcedureId = procedureId == Guid.Empty
|
|
? Guid.Parse("82722c9b-cecc-4299-8a7b-ce5abeb8170b")
|
|
: procedureId;
|
|
|
|
var url = await ResolveExternalProcedureCreateFileUrlAsync(effectiveProcedureId);
|
|
using var req = new HttpRequestMessage(HttpMethod.Post, url);
|
|
req.Headers.Accept.Clear();
|
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"CreateFileAsync: {(int)resp.StatusCode} {resp.StatusCode}\n{body}");
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
var fileUrl = GetLinkHref(doc.RootElement, "file")
|
|
?? throw new InvalidOperationException("CreateFileAsync: Gestiona no ha devuelto link 'file'.");
|
|
var fileOpenUrl = GetLinkHref(doc.RootElement, "file-open");
|
|
|
|
return new GestionaCreateFileResponse(fileUrl, fileOpenUrl);
|
|
}
|
|
|
|
private async Task<string> ResolveExternalProcedureCreateFileUrlAsync(Guid procedureId)
|
|
{
|
|
if (Guid.TryParse(_opts.ExternalProcedureId, out var configuredExternalProcedureId))
|
|
{
|
|
return $"/rest/catalog-2015/procedures/{procedureId}/external-procedures/{configuredExternalProcedureId}/create-file";
|
|
}
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, $"/rest/catalog-2015/procedures/{procedureId}/external-procedures");
|
|
AddTokenAndAccept(req, "application/vnd.gestiona.external-procedures-page+json");
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
if (resp.StatusCode == System.Net.HttpStatusCode.NoContent)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"El procedimiento {procedureId} no tiene tramites externos configurados en Gestiona.");
|
|
}
|
|
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"ResolveExternalProcedureCreateFileUrlAsync: {(int)resp.StatusCode} {resp.StatusCode}\n{body}");
|
|
}
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
if (!doc.RootElement.TryGetProperty("content", out var content) || content.ValueKind != JsonValueKind.Array)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"El procedimiento {procedureId} no ha devuelto tramites externos validos en Gestiona.");
|
|
}
|
|
|
|
var createFileCandidates = new List<(string? Id, string Href)>();
|
|
foreach (var item in content.EnumerateArray())
|
|
{
|
|
var createFileHref = GetLinkHref(item, "create-file");
|
|
if (!string.IsNullOrWhiteSpace(createFileHref))
|
|
{
|
|
var externalProcedureId = item.TryGetProperty("id", out var idProp) && idProp.ValueKind == JsonValueKind.String
|
|
? idProp.GetString()
|
|
: null;
|
|
createFileCandidates.Add((externalProcedureId, createFileHref!));
|
|
}
|
|
}
|
|
|
|
if (createFileCandidates.Count == 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"El procedimiento {procedureId} no tiene ningun tramite externo con link create-file.");
|
|
}
|
|
|
|
return createFileCandidates
|
|
.FirstOrDefault(candidate => string.Equals(candidate.Id, procedureId.ToString(), StringComparison.OrdinalIgnoreCase))
|
|
.Href
|
|
?? createFileCandidates[0].Href;
|
|
}
|
|
|
|
public async Task OpenFileAsync(
|
|
string fileUrl,
|
|
string? fileOpenUrl,
|
|
Guid managementUnitGroupId,
|
|
Guid assignedGroupId,
|
|
bool confidential,
|
|
string freeTitle,
|
|
string siaCode)
|
|
{
|
|
var url = string.IsNullOrWhiteSpace(fileOpenUrl)
|
|
? $"{fileUrl.TrimEnd('/')}/open"
|
|
: fileOpenUrl;
|
|
|
|
var payload = new
|
|
{
|
|
free_title = freeTitle,
|
|
location = siaCode,
|
|
entry_date = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),
|
|
confidential,
|
|
initial_assignation = new[]
|
|
{
|
|
new { rel = "group", href = $"{_opts.ApiBase}/rest/groups/{assignedGroupId}" }
|
|
},
|
|
links = new[]
|
|
{
|
|
new { rel = "management-unit-group", href = $"{_opts.ApiBase}/rest/groups/{managementUnitGroupId}" }
|
|
}
|
|
};
|
|
|
|
var json = JsonSerializer.Serialize(payload);
|
|
using var content = new StringContent(json, Encoding.UTF8, "application/vnd.gestiona.file-opening+json");
|
|
content.Headers.ContentType!.Parameters.Add(new NameValueHeaderValue("version", "1"));
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
|
|
AddTokenAndAccept(req, "application/json");
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"OpenFileAsync: {(int)resp.StatusCode} {resp.StatusCode}\n{body}");
|
|
}
|
|
|
|
public async Task<Guid> CreateFolderAsync(string fileUrl, string folderName)
|
|
{
|
|
var endpoint = $"{fileUrl}/documents-and-folders";
|
|
var payload = new { name = folderName };
|
|
var json = JsonSerializer.Serialize(payload);
|
|
using var content = new StringContent(json, Encoding.UTF8, "application/vnd.gestiona.file-folder+json");
|
|
content.Headers.ContentType!.Parameters.Add(new NameValueHeaderValue("version", "1"));
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint)
|
|
{
|
|
Content = content
|
|
};
|
|
AddTokenAndAccept(req, "application/json");
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"CreateFolderAsync: {(int)resp.StatusCode} {resp.StatusCode}\n{body}");
|
|
|
|
var selfHref = TryGetSelfHref(body) ?? resp.Headers.Location?.ToString();
|
|
if (string.IsNullOrWhiteSpace(selfHref))
|
|
{
|
|
throw new InvalidOperationException("CreateFolderAsync: Gestiona no ha devuelto link self ni Location.");
|
|
}
|
|
|
|
return Guid.Parse(selfHref.TrimEnd('/').Split('/').Last());
|
|
}
|
|
|
|
public async Task<string> CreateUploadAsync(byte[] contentBytes, string fileName)
|
|
{
|
|
using var createReq = new HttpRequestMessage(HttpMethod.Post, "/rest/uploads");
|
|
createReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
createReq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
using var createResp = await _http.SendAsync(createReq);
|
|
var createBody = await createResp.Content.ReadAsStringAsync();
|
|
if (!createResp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"CreateUpload (POST): {(int)createResp.StatusCode} {createResp.StatusCode}\n{createBody}");
|
|
|
|
var uploadUri = createResp.Headers.Location?.ToString()
|
|
?? throw new InvalidOperationException("No se devolvió Location en /rest/uploads");
|
|
|
|
string md5Hex;
|
|
using (var md5 = MD5.Create())
|
|
{
|
|
var hash = md5.ComputeHash(contentBytes);
|
|
md5Hex = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
|
}
|
|
|
|
using var putReq = new HttpRequestMessage(HttpMethod.Put, uploadUri);
|
|
putReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
putReq.Headers.TryAddWithoutValidation("X-Gestiona-Upload-MD5", md5Hex);
|
|
putReq.Headers.TryAddWithoutValidation("Slug", fileName);
|
|
putReq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
putReq.Content = new ByteArrayContent(contentBytes);
|
|
putReq.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
|
|
|
|
using var putResp = await _http.SendAsync(putReq);
|
|
var infoJson = await putResp.Content.ReadAsStringAsync();
|
|
if (!putResp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"CreateUpload (PUT): {(int)putResp.StatusCode} {putResp.StatusCode}\n{infoJson}");
|
|
|
|
using var infoDoc = JsonDocument.Parse(infoJson);
|
|
var status = infoDoc.RootElement.GetProperty("status").GetString();
|
|
if (status != "READY")
|
|
throw new InvalidOperationException($"Upload no READY: {status}");
|
|
|
|
return uploadUri;
|
|
}
|
|
|
|
public async Task UploadDocumentAsync(string fileUrl, byte[] contentBytes, string fileName)
|
|
{
|
|
var uploadUri = await CreateUploadAsync(contentBytes, fileName);
|
|
|
|
var metaPayload = new
|
|
{
|
|
type = "DIGITAL",
|
|
name = fileName,
|
|
description = "Denuncia combinada",
|
|
elaboration_state = "EE01",
|
|
metadata_language = "ES",
|
|
links = new[] { new { rel = "content", href = uploadUri } }
|
|
};
|
|
|
|
var metaJson = JsonSerializer.Serialize(metaPayload);
|
|
using var metaContent = new StringContent(metaJson, Encoding.UTF8);
|
|
metaContent.Headers.ContentType =
|
|
MediaTypeHeaderValue.Parse("application/vnd.gestiona.file-document+json; version=4");
|
|
|
|
using var metaReq = new HttpRequestMessage(HttpMethod.Post, $"{fileUrl}/documents-and-folders")
|
|
{ Content = metaContent };
|
|
metaReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
metaReq.Headers.Accept.Clear();
|
|
metaReq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
using var metaResp = await _http.SendAsync(metaReq);
|
|
var body = await metaResp.Content.ReadAsStringAsync();
|
|
if (!metaResp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"UploadDocumentAsync: {(int)metaResp.StatusCode} {metaResp.StatusCode}\n{body}");
|
|
}
|
|
|
|
private static string? TryGetSelfHref(string body)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(body))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
using var doc = JsonDocument.Parse(body);
|
|
return GetLinkHref(doc.RootElement, "self");
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// TERCEROS
|
|
// =========================
|
|
|
|
public async Task<(string Id, string SelfHref)> BuscarTerceroPorNifAsync(string nif)
|
|
{
|
|
var filtro = new
|
|
{
|
|
result = new { max_results = 25 },
|
|
filter = new { nif }
|
|
};
|
|
var jsonFiltro = JsonSerializer.Serialize(filtro);
|
|
var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonFiltro));
|
|
var url = $"{_opts.ApiBase}/rest/thirds?filter-view={Uri.EscapeDataString(b64)}";
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, url);
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.TryAddWithoutValidation("Accept", "application/vnd.gestiona.thirds-page+json");
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
resp.EnsureSuccessStatusCode();
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
if (!doc.RootElement.TryGetProperty("content", out var content) || content.ValueKind != JsonValueKind.Array)
|
|
return default;
|
|
|
|
var match = content.EnumerateArray().FirstOrDefault(e =>
|
|
e.TryGetProperty("nif", out var nifProp) && nifProp.GetString() == nif);
|
|
|
|
if (match.ValueKind == JsonValueKind.Undefined) return default;
|
|
|
|
var id = match.GetProperty("id").GetString()!;
|
|
var self = match.GetProperty("links").EnumerateArray()
|
|
.First(l => l.GetProperty("rel").GetString() == "self")
|
|
.GetProperty("href").GetString()!;
|
|
return (id, self);
|
|
}
|
|
|
|
public async Task<(string Id, string SelfHref)> CrearTerceroAsync(ThirdPartyIdentityData thirdParty)
|
|
{
|
|
if (thirdParty is null)
|
|
throw new ArgumentNullException(nameof(thirdParty));
|
|
|
|
if (string.IsNullOrWhiteSpace(thirdParty.DocumentId))
|
|
throw new ArgumentException("Documento identificativo obligatorio.", nameof(thirdParty));
|
|
|
|
if (thirdParty.IsLegalEntity)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(thirdParty.BusinessName))
|
|
throw new ArgumentException("La razón social es obligatoria para terceros jurídicos.", nameof(thirdParty));
|
|
}
|
|
else
|
|
{
|
|
if (string.IsNullOrWhiteSpace(thirdParty.FirstName))
|
|
throw new ArgumentException("Nombre obligatorio.", nameof(thirdParty));
|
|
if (string.IsNullOrWhiteSpace(thirdParty.LastName))
|
|
throw new ArgumentException("Primer apellido obligatorio.", nameof(thirdParty));
|
|
}
|
|
|
|
var payload = new Dictionary<string, object?>
|
|
{
|
|
["nif_country"] = NormalizeCountryCode(
|
|
string.IsNullOrWhiteSpace(thirdParty.CountryCode)
|
|
? thirdParty.Address?.CountryCode
|
|
: thirdParty.CountryCode),
|
|
["nif"] = thirdParty.DocumentId.Trim(),
|
|
["type"] = thirdParty.IsLegalEntity ? "JURIDICAL" : "PHISIC",
|
|
["notification_channel"] = BuildNotificationChannel(thirdParty)
|
|
};
|
|
var nifType = GuessNifType(thirdParty.DocumentId, thirdParty.IsLegalEntity);
|
|
if (!string.IsNullOrWhiteSpace(nifType))
|
|
{
|
|
payload["nif_type"] = nifType;
|
|
}
|
|
|
|
if (thirdParty.IsLegalEntity)
|
|
{
|
|
payload["business_name"] = thirdParty.BusinessName.Trim();
|
|
}
|
|
else
|
|
{
|
|
var (firstSurname, secondSurname) = SplitSurnames(thirdParty.LastName);
|
|
payload["first_name"] = thirdParty.FirstName.Trim();
|
|
payload["first_surname"] = firstSurname;
|
|
if (!string.IsNullOrWhiteSpace(secondSurname))
|
|
payload["second_surname"] = secondSurname;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(thirdParty.Email))
|
|
payload["email"] = thirdParty.Email.Trim();
|
|
|
|
var jsonOpts = new JsonSerializerOptions
|
|
{
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
|
};
|
|
var json = JsonSerializer.Serialize(payload, jsonOpts);
|
|
|
|
using var content = new StringContent(json, Encoding.UTF8, "application/vnd.gestiona.third+json");
|
|
content.Headers.ContentType!.Parameters.Add(new NameValueHeaderValue("version", "3"));
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Post, "/rest/thirds")
|
|
{
|
|
Content = content
|
|
};
|
|
req.Headers.Accept.Clear();
|
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.gestiona.third+json"));
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException(
|
|
$"Error CrearTerceroAsync: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{body}");
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
var id = doc.RootElement.GetProperty("id").GetString()!;
|
|
var selfHref = doc.RootElement.GetProperty("links").EnumerateArray()
|
|
.First(l => l.GetProperty("rel").GetString() == "self")
|
|
.GetProperty("href").GetString()!;
|
|
|
|
if (thirdParty.Address?.HasAnyValue == true)
|
|
{
|
|
await TryEnsureThirdAddressAsync(selfHref, thirdParty.Address);
|
|
}
|
|
|
|
return (id, selfHref);
|
|
}
|
|
|
|
public async Task<HashSet<string>> ObtenerTercerosEnlazadosAsync(string fileUrl)
|
|
{
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, $"{fileUrl}/thirdparties");
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.TryAddWithoutValidation("Accept", "*/*");
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
if (resp.StatusCode == System.Net.HttpStatusCode.NoContent)
|
|
return new HashSet<string>(StringComparer.Ordinal);
|
|
|
|
resp.EnsureSuccessStatusCode();
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
var set = new HashSet<string>(StringComparer.Ordinal);
|
|
if (doc.RootElement.TryGetProperty("content", out var content) && content.ValueKind == JsonValueKind.Array)
|
|
{
|
|
foreach (var item in content.EnumerateArray())
|
|
{
|
|
if (item.TryGetProperty("links", out var links))
|
|
{
|
|
var third = links.EnumerateArray().FirstOrDefault(l => l.GetProperty("rel").GetString() == "third");
|
|
if (third.ValueKind != JsonValueKind.Undefined)
|
|
set.Add(third.GetProperty("href").GetString()!);
|
|
}
|
|
}
|
|
}
|
|
return set;
|
|
}
|
|
|
|
public async Task EnlazarTerceroExistenteAsync(string fileUrl, string thirdSelfHref)
|
|
{
|
|
var payload = new { href = thirdSelfHref };
|
|
var json = JsonSerializer.Serialize(payload);
|
|
using var content = new StringContent(json, Encoding.UTF8);
|
|
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/vnd.gestiona.third-link+json");
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Post, $"{fileUrl}/thirdparties");
|
|
req.Content = content;
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.TryAddWithoutValidation("Accept", "*/*");
|
|
|
|
var resp = await _http.SendAsync(req);
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"Error EnlazarTerceroExistenteAsync: {resp.StatusCode}\n{body}");
|
|
}
|
|
|
|
public async Task AsegurarTerceroYEnlazarAsync(string fileUrl, ThirdPartyIdentityData thirdParty)
|
|
{
|
|
if (thirdParty is null)
|
|
throw new ArgumentNullException(nameof(thirdParty));
|
|
|
|
thirdParty = NormalizeThirdParty(thirdParty);
|
|
|
|
if (string.IsNullOrWhiteSpace(thirdParty.DocumentId)) return;
|
|
|
|
var encontrado = await BuscarTerceroPorNifAsync(thirdParty.DocumentId);
|
|
|
|
if (string.IsNullOrEmpty(encontrado.SelfHref))
|
|
{
|
|
encontrado = await CrearTerceroAsync(thirdParty);
|
|
}
|
|
else if (thirdParty.Address?.HasAnyValue == true)
|
|
{
|
|
await TryEnsureThirdAddressAsync(encontrado.SelfHref, thirdParty.Address);
|
|
}
|
|
|
|
var yaEnlazados = await ObtenerTercerosEnlazadosAsync(fileUrl);
|
|
if (!yaEnlazados.Contains(encontrado.SelfHref))
|
|
await EnlazarTerceroExistenteAsync(fileUrl, encontrado.SelfHref);
|
|
}
|
|
|
|
private static ThirdPartyIdentityData NormalizeThirdParty(ThirdPartyIdentityData thirdParty)
|
|
{
|
|
if (!thirdParty.IsAnonymous)
|
|
{
|
|
return thirdParty;
|
|
}
|
|
|
|
return new ThirdPartyIdentityData
|
|
{
|
|
IsAnonymous = true,
|
|
IsLegalEntity = false,
|
|
DocumentId = "00000000T",
|
|
FirstName = "Anonimo",
|
|
LastName = "-",
|
|
BusinessName = string.Empty,
|
|
Email = string.Empty,
|
|
CountryCode = string.IsNullOrWhiteSpace(thirdParty.CountryCode) ? "ESP" : thirdParty.CountryCode,
|
|
Address = null
|
|
};
|
|
}
|
|
|
|
// --- CONSULTAS DE EXPEDIENTES (sin recorrer histórico paginado) ---
|
|
|
|
private async Task<string> GetFilesAsync(object? filter = null)
|
|
{
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, "/rest/files");
|
|
AddBasicHeaders(req);
|
|
|
|
if (filter is not null)
|
|
{
|
|
var json = JsonSerializer.Serialize(filter);
|
|
req.Content = new StringContent(json, Encoding.UTF8, "application/vnd.gestiona.filter.files");
|
|
}
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
if (resp.StatusCode == System.Net.HttpStatusCode.NoContent)
|
|
{
|
|
return "{\"content\":[]}";
|
|
}
|
|
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"GET /rest/files: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{body}");
|
|
|
|
if (string.IsNullOrWhiteSpace(body))
|
|
{
|
|
return "{\"content\":[]}";
|
|
}
|
|
|
|
return body;
|
|
}
|
|
|
|
private static bool TryGetFilesContent(JsonDocument doc, out JsonElement content)
|
|
{
|
|
if (doc.RootElement.TryGetProperty("content", out var contentElement) &&
|
|
contentElement.ValueKind == JsonValueKind.Array)
|
|
{
|
|
content = contentElement;
|
|
return true;
|
|
}
|
|
|
|
if (doc.RootElement.ValueKind == JsonValueKind.Array)
|
|
{
|
|
content = doc.RootElement;
|
|
return true;
|
|
}
|
|
|
|
content = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Devuelve el JSON crudo de /rest/files acumulando hasta maxPages páginas.
|
|
/// </summary>
|
|
public async Task<string> ListarExpedientesJsonAsyncBasico(int maxPages = 1)
|
|
{
|
|
_ = maxPages;
|
|
|
|
var json = await GetFilesAsync();
|
|
using var doc = JsonDocument.Parse(json);
|
|
|
|
if (!TryGetFilesContent(doc, out var content))
|
|
{
|
|
return "[]";
|
|
}
|
|
|
|
var sb = new StringBuilder();
|
|
sb.Append('[');
|
|
|
|
var first = true;
|
|
foreach (var item in content.EnumerateArray())
|
|
{
|
|
if (!first)
|
|
{
|
|
sb.Append(',');
|
|
}
|
|
|
|
sb.Append(item.GetRawText());
|
|
first = false;
|
|
}
|
|
|
|
sb.Append(']');
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Busca un expediente cuyo asunto sea "Denuncia {idDenuncia}-CD".
|
|
/// </summary>
|
|
public async Task<GestionaExpedienteInfo?> BuscarExpedientePorIdEnAsuntoAsync(int idDenuncia)
|
|
{
|
|
var needle = $"Denuncia {idDenuncia}-CD";
|
|
|
|
var json = await GetFilesAsync(new
|
|
{
|
|
subject = needle
|
|
});
|
|
using var doc = JsonDocument.Parse(json);
|
|
|
|
if (!TryGetFilesContent(doc, out var content) || content.GetArrayLength() == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (var item in content.EnumerateArray())
|
|
{
|
|
var expediente = BuildExpedienteInfo(item);
|
|
if (expediente is not null)
|
|
{
|
|
return expediente;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public async Task<GestionaExpedienteInfo?> ObtenerExpedienteAsync(string fileUrl)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(fileUrl))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, fileUrl);
|
|
AddTokenAndAccept(req, "application/json");
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
if (resp.StatusCode == System.Net.HttpStatusCode.NoContent ||
|
|
resp.StatusCode == System.Net.HttpStatusCode.NotFound)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"ObtenerExpedienteAsync: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{body}");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(body))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
return BuildExpedienteInfo(doc.RootElement, fileUrl);
|
|
}
|
|
|
|
private static GestionaExpedienteInfo? BuildExpedienteInfo(JsonElement item, string? fallbackFileUrl = null)
|
|
{
|
|
var code = GetJsonString(item, "code");
|
|
var freeTitle = GetJsonString(item, "free_title");
|
|
var subject = GetJsonString(item, "subject");
|
|
var selectableTitle = GetJsonString(item, "selectable_title");
|
|
var procedureName = GetJsonString(item, "procedure_name");
|
|
|
|
string? fileUrl = fallbackFileUrl;
|
|
if (item.TryGetProperty("links", out var links) && links.ValueKind == JsonValueKind.Array)
|
|
{
|
|
foreach (var link in links.EnumerateArray())
|
|
{
|
|
if (link.TryGetProperty("rel", out var rel) &&
|
|
string.Equals(rel.GetString(), "file", StringComparison.OrdinalIgnoreCase) &&
|
|
link.TryGetProperty("href", out var href) &&
|
|
href.ValueKind == JsonValueKind.String)
|
|
{
|
|
fileUrl = href.GetString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(fileUrl) &&
|
|
item.TryGetProperty("id", out var idProp) &&
|
|
idProp.ValueKind == JsonValueKind.String)
|
|
{
|
|
fileUrl = $"/rest/files/{idProp.GetString()}";
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(fileUrl))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new GestionaExpedienteInfo
|
|
{
|
|
FileUrl = fileUrl,
|
|
CodigoExpediente = code,
|
|
FreeTitle = FirstNonEmpty(freeTitle, subject, selectableTitle, procedureName)
|
|
};
|
|
}
|
|
|
|
|
|
public async Task<List<ExpedienteTerceroDto>> ObtenerExpedientesPorTerceroAsync(
|
|
string nif,
|
|
DateTimeOffset? desde = null,
|
|
DateTimeOffset? hasta = null,
|
|
int maxPages = 1,
|
|
int maxResults = 30,
|
|
int maxParallel = 6 // de momento NO se usa, dejamos la firma por compatibilidad
|
|
)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(nif))
|
|
throw new ArgumentException("NIF obligatorio.", nameof(nif));
|
|
|
|
nif = nif.Trim().ToUpperInvariant();
|
|
_ = maxPages;
|
|
_ = maxParallel;
|
|
|
|
// 1) Localizar el tercero por NIF
|
|
var tercero = await BuscarTerceroPorNifAsync(nif);
|
|
if (string.IsNullOrEmpty(tercero.SelfHref))
|
|
return new List<ExpedienteTerceroDto>(); // no hay tercero => no hay expedientes
|
|
|
|
var resultados = new List<ExpedienteTerceroDto>();
|
|
var json = await GetFilesAsync(new
|
|
{
|
|
third_rest_link = new
|
|
{
|
|
rel = "third",
|
|
href = tercero.SelfHref
|
|
}
|
|
});
|
|
using var doc = JsonDocument.Parse(json);
|
|
|
|
if (!TryGetFilesContent(doc, out var content) || content.GetArrayLength() == 0)
|
|
{
|
|
return resultados;
|
|
}
|
|
|
|
foreach (var item in content.EnumerateArray())
|
|
{
|
|
if (resultados.Count >= maxResults)
|
|
{
|
|
break;
|
|
}
|
|
|
|
DateTimeOffset? creation = null;
|
|
if (item.TryGetProperty("creation_date", out var pCreation))
|
|
{
|
|
if (pCreation.ValueKind == JsonValueKind.Number &&
|
|
pCreation.TryGetInt64(out var ts))
|
|
{
|
|
creation = DateTimeOffset.FromUnixTimeSeconds(ts);
|
|
}
|
|
else if (pCreation.ValueKind == JsonValueKind.String &&
|
|
long.TryParse(pCreation.GetString(), out var tsString))
|
|
{
|
|
creation = DateTimeOffset.FromUnixTimeSeconds(tsString);
|
|
}
|
|
}
|
|
|
|
if (desde.HasValue && creation.HasValue && creation.Value < desde.Value)
|
|
continue;
|
|
|
|
if (hasta.HasValue && creation.HasValue && creation.Value > hasta.Value)
|
|
continue;
|
|
|
|
string? fileUrl = null;
|
|
if (item.TryGetProperty("links", out var links) && links.ValueKind == JsonValueKind.Array)
|
|
{
|
|
foreach (var l in links.EnumerateArray())
|
|
{
|
|
if (l.TryGetProperty("rel", out var relProp) &&
|
|
string.Equals(relProp.GetString(), "file", StringComparison.OrdinalIgnoreCase) &&
|
|
l.TryGetProperty("href", out var hrefProp))
|
|
{
|
|
fileUrl = hrefProp.GetString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(fileUrl) &&
|
|
item.TryGetProperty("id", out var idProp) &&
|
|
idProp.ValueKind == JsonValueKind.String)
|
|
{
|
|
fileUrl = $"/rest/files/{idProp.GetString()}";
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(fileUrl))
|
|
continue;
|
|
|
|
var code = GetJsonString(item, "code");
|
|
var subject = GetJsonString(item, "subject");
|
|
var freeTitle = GetJsonString(item, "free_title");
|
|
var selectableTitle = GetJsonString(item, "selectable_title");
|
|
var procedureName = GetJsonString(item, "procedure_name");
|
|
var asunto = FirstNonEmpty(freeTitle, subject, selectableTitle, procedureName);
|
|
|
|
string? state = null;
|
|
if (item.TryGetProperty("state", out var pState) && pState.ValueKind == JsonValueKind.String)
|
|
state = pState.GetString();
|
|
else if (item.TryGetProperty("status", out var pStatus) && pStatus.ValueKind == JsonValueKind.String)
|
|
state = pStatus.GetString();
|
|
|
|
resultados.Add(new ExpedienteTerceroDto
|
|
{
|
|
FileUrl = fileUrl,
|
|
CodigoExpediente = code,
|
|
Asunto = asunto,
|
|
FechaCreacion = creation,
|
|
Estado = state
|
|
});
|
|
}
|
|
|
|
return resultados;
|
|
}
|
|
|
|
private async Task TryEnsureThirdAddressAsync(string thirdSelfHref, ThirdPartyAddressData address)
|
|
{
|
|
if (!address.HasAnyValue)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (await ThirdHasAddressesAsync(thirdSelfHref))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var addressPayload = await BuildAddressPayloadAsync(address);
|
|
if (addressPayload is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var payload = new Dictionary<string, object?>
|
|
{
|
|
["content"] = new[] { addressPayload }
|
|
};
|
|
|
|
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions
|
|
{
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
|
});
|
|
|
|
using var content = new StringContent(json, Encoding.UTF8, "application/vnd.gestiona.third-addresses+json");
|
|
content.Headers.ContentType!.Parameters.Add(new NameValueHeaderValue("version", "2"));
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Put, $"{thirdSelfHref.TrimEnd('/')}/addresses")
|
|
{
|
|
Content = content
|
|
};
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new InvalidOperationException($"Error actualizando dirección del tercero: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{body}");
|
|
}
|
|
|
|
private async Task<bool> ThirdHasAddressesAsync(string thirdSelfHref)
|
|
{
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, $"{thirdSelfHref.TrimEnd('/')}/addresses");
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
if (resp.StatusCode == System.Net.HttpStatusCode.NoContent ||
|
|
resp.StatusCode == System.Net.HttpStatusCode.NotFound)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
resp.EnsureSuccessStatusCode();
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse(body);
|
|
return doc.RootElement.TryGetProperty("content", out var content) &&
|
|
content.ValueKind == JsonValueKind.Array &&
|
|
content.GetArrayLength() > 0;
|
|
}
|
|
|
|
private async Task<Dictionary<string, object?>?> BuildAddressPayloadAsync(ThirdPartyAddressData address)
|
|
{
|
|
var provinceCode = await ResolveProvinceCodeAsync(address.Province, NormalizeCountryCode(address.CountryCode));
|
|
var townHref = await ResolveTownHrefAsync(provinceCode, address.Municipality);
|
|
|
|
if (string.IsNullOrWhiteSpace(address.Street) &&
|
|
string.IsNullOrWhiteSpace(address.Municipality) &&
|
|
string.IsNullOrWhiteSpace(address.ZipCode))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var payload = new Dictionary<string, object?>
|
|
{
|
|
["address"] = address.Street,
|
|
["number"] = address.Number,
|
|
["floor"] = address.Floor,
|
|
["door"] = address.Door,
|
|
["block"] = address.Block,
|
|
["stair"] = address.Stair,
|
|
["zipcode"] = address.ZipCode,
|
|
["country"] = NormalizeCountryCode(address.CountryCode),
|
|
["province"] = provinceCode,
|
|
["type_of_road"] = string.IsNullOrWhiteSpace(address.RoadTypeCode) ? "CL" : address.RoadTypeCode,
|
|
["default_address"] = true,
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(townHref))
|
|
{
|
|
payload["links"] = new[]
|
|
{
|
|
new Dictionary<string, string>
|
|
{
|
|
["rel"] = "town",
|
|
["href"] = townHref
|
|
}
|
|
};
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(address.Municipality))
|
|
{
|
|
payload["zone"] = address.Municipality.Trim();
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
private async Task<string?> ResolveProvinceCodeAsync(string? province, string countryCode)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(province))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var normalizedSearch = NormalizeKey(province);
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, "/rest/provinces");
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
resp.EnsureSuccessStatusCode();
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse(body);
|
|
|
|
var collection = doc.RootElement.TryGetProperty("content", out var content) &&
|
|
content.ValueKind == JsonValueKind.Array
|
|
? content
|
|
: doc.RootElement;
|
|
|
|
if (collection.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return province.Trim().ToUpperInvariant();
|
|
}
|
|
|
|
foreach (var item in collection.EnumerateArray())
|
|
{
|
|
var code = item.TryGetProperty("code", out var codeProp) ? codeProp.GetString() : null;
|
|
var name = item.TryGetProperty("name", out var nameProp) ? nameProp.GetString() : null;
|
|
var itemCountry = item.TryGetProperty("country_code", out var countryProp) ? countryProp.GetString() : null;
|
|
|
|
if (!string.IsNullOrWhiteSpace(itemCountry) &&
|
|
!string.Equals(NormalizeCountryCode(itemCountry), countryCode, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (NormalizeKey(code) == normalizedSearch || NormalizeKey(name) == normalizedSearch)
|
|
{
|
|
return code ?? province.Trim().ToUpperInvariant();
|
|
}
|
|
}
|
|
|
|
return province.Trim().ToUpperInvariant();
|
|
}
|
|
|
|
private async Task<string?> ResolveTownHrefAsync(string? provinceCode, string? municipality)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(provinceCode) || string.IsNullOrWhiteSpace(municipality))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, $"/rest/provinces/{Uri.EscapeDataString(provinceCode)}/towns");
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
using var resp = await _http.SendAsync(req);
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse(body);
|
|
|
|
var collection = doc.RootElement.TryGetProperty("content", out var content) &&
|
|
content.ValueKind == JsonValueKind.Array
|
|
? content
|
|
: doc.RootElement;
|
|
|
|
if (collection.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var normalizedSearch = NormalizeKey(municipality);
|
|
foreach (var item in collection.EnumerateArray())
|
|
{
|
|
var name = item.TryGetProperty("name", out var nameProp) ? nameProp.GetString() : null;
|
|
if (NormalizeKey(name) != normalizedSearch)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!item.TryGetProperty("links", out var links) || links.ValueKind != JsonValueKind.Array)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var link in links.EnumerateArray())
|
|
{
|
|
if (link.TryGetProperty("rel", out var relProp) &&
|
|
string.Equals(relProp.GetString(), "self", StringComparison.OrdinalIgnoreCase) &&
|
|
link.TryGetProperty("href", out var hrefProp))
|
|
{
|
|
return hrefProp.GetString();
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? GetJsonString(JsonElement item, string propertyName)
|
|
{
|
|
return item.TryGetProperty(propertyName, out var property) &&
|
|
property.ValueKind == JsonValueKind.String
|
|
? property.GetString()
|
|
: null;
|
|
}
|
|
|
|
private static string? FirstNonEmpty(params string?[] values)
|
|
{
|
|
foreach (var value in values)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static (string FirstSurname, string? SecondSurname) SplitSurnames(string apellidos)
|
|
{
|
|
var parts = (apellidos ?? string.Empty)
|
|
.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
if (parts.Length == 0)
|
|
{
|
|
return ("-", null);
|
|
}
|
|
|
|
if (parts.Length == 1)
|
|
{
|
|
return (parts[0], null);
|
|
}
|
|
|
|
return (parts[0], string.Join(' ', parts.Skip(1)));
|
|
}
|
|
|
|
private static string BuildNotificationChannel(ThirdPartyIdentityData thirdParty)
|
|
{
|
|
_ = thirdParty;
|
|
return "TELEMATIC";
|
|
}
|
|
|
|
private static string? GuessNifType(string nif, bool isLegalEntity)
|
|
{
|
|
var value = (nif ?? string.Empty).Trim().ToUpperInvariant();
|
|
if (isLegalEntity && Regex.IsMatch(value, @"^[A-Z]\d{7}[A-Z0-9]$"))
|
|
{
|
|
return "CIF";
|
|
}
|
|
|
|
if (value.StartsWith("X", StringComparison.Ordinal) ||
|
|
value.StartsWith("Y", StringComparison.Ordinal) ||
|
|
value.StartsWith("Z", StringComparison.Ordinal))
|
|
{
|
|
return "NIE";
|
|
}
|
|
|
|
if (Regex.IsMatch(value, @"^\d{7,8}[A-Z0-9]$"))
|
|
{
|
|
return "NIF";
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string NormalizeCountryCode(string? country)
|
|
{
|
|
var value = NormalizeKey(country);
|
|
return value switch
|
|
{
|
|
"" => "ESP",
|
|
"es" or "esp" or "espana" or "españa" or "spain" => "ESP",
|
|
"prt" or "pt" or "portugal" => "PRT",
|
|
_ when country is { Length: >= 3 } => country.Trim().ToUpperInvariant()[..3],
|
|
_ => "ESP",
|
|
};
|
|
}
|
|
|
|
private static string NormalizeKey(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var normalized = value.Normalize(NormalizationForm.FormD);
|
|
var builder = new StringBuilder(normalized.Length);
|
|
|
|
foreach (var character in normalized)
|
|
{
|
|
var category = CharUnicodeInfo.GetUnicodeCategory(character);
|
|
if (category == UnicodeCategory.NonSpacingMark)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
builder.Append(char.IsLetterOrDigit(character) ? char.ToUpperInvariant(character) : ' ');
|
|
}
|
|
|
|
return string.Join(' ', builder.ToString().Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|