- GeradoresService: cada tipo passa a ser um IGenerator (NIF, NIPC, NISS, CC, passaporte, IBAN, NIB, cartões, referência MB, pessoa, telefone, código postal, email, matrícula, EAN, UUID) registado em GeneratorRegistry; remove DAL/EF - GeradoresWS: minimal APIs em /api/v1 com OpenAPI + Scalar e health check - UnitTest: testes de ida e volta, valores conhecidos, detecção e API - geradoresfe: UI construída a partir dos metadados da API, validador livre, exportação CSV/JSON/TXT, nginx com API_UPSTREAM configurável - Docker, docker-compose e Jenkinsfile actualizados Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
3.3 KiB
C#
76 lines
3.3 KiB
C#
using GeradoresService.Core;
|
|
|
|
namespace GeradoresService.Generators.Financeiro;
|
|
|
|
/// <summary>
|
|
/// IBAN português: PT + 2 dígitos de controlo ISO 7064 (MOD 97-10) + NIB.
|
|
/// A validação aceita IBAN de qualquer país (verifica comprimento conhecido e MOD 97).
|
|
/// </summary>
|
|
public sealed class IbanGenerator : NibGenerator
|
|
{
|
|
private static readonly Dictionary<string, int> Lengths = new()
|
|
{
|
|
["PT"] = 25, ["ES"] = 24, ["FR"] = 27, ["DE"] = 22, ["IT"] = 27, ["GB"] = 22, ["IE"] = 22,
|
|
["NL"] = 18, ["BE"] = 16, ["LU"] = 20, ["CH"] = 21, ["AT"] = 20, ["DK"] = 18, ["FI"] = 18,
|
|
["SE"] = 24, ["NO"] = 15, ["PL"] = 28, ["CZ"] = 24, ["GR"] = 27, ["HR"] = 21, ["RO"] = 24,
|
|
["MT"] = 31, ["CY"] = 28, ["SK"] = 24, ["SI"] = 19, ["HU"] = 28, ["BG"] = 22, ["EE"] = 20,
|
|
["LT"] = 20, ["LV"] = 21, ["IS"] = 26, ["LI"] = 21, ["MC"] = 27, ["SM"] = 27, ["BR"] = 29,
|
|
};
|
|
|
|
public override string Id => "iban";
|
|
public override string Name => "IBAN";
|
|
public override string Description => "IBAN português (PT50 + NIB). Valida IBAN de qualquer país.";
|
|
|
|
public override IReadOnlyList<GeneratorOption> Options =>
|
|
[
|
|
.. base.Options,
|
|
Option("formato", "Formato", "espacos", ("espacos", "PT50 0000 0000 …"), ("compacto", "PT500000…")),
|
|
];
|
|
|
|
public override GeneratedItem Generate(GeneratorContext context)
|
|
{
|
|
var iban = FromBban("PT", BuildNib(context));
|
|
return new GeneratedItem(context.Option(Options[1]) == "compacto" ? iban : Format(iban));
|
|
}
|
|
|
|
public static string FromBban(string country, string bban)
|
|
{
|
|
var check = 98 - Checksums.Mod97(Checksums.LettersToDigits(bban + country) + "00");
|
|
return $"{country}{check:D2}{bban}";
|
|
}
|
|
|
|
public static string Format(string iban) =>
|
|
string.Join(' ', iban.Chunk(4).Select(c => new string(c)));
|
|
|
|
public override ValidationResult Validate(string value)
|
|
{
|
|
var iban = Checksums.Clean(value).ToUpperInvariant();
|
|
if (iban.Length < 15 || iban.Length > 34
|
|
|| !char.IsAsciiLetter(iban[0]) || !char.IsAsciiLetter(iban[1])
|
|
|| !char.IsAsciiDigit(iban[2]) || !char.IsAsciiDigit(iban[3])
|
|
|| !iban.All(char.IsAsciiLetterOrDigit))
|
|
return ValidationResult.WrongFormat("Um IBAN tem código de país (2 letras), 2 dígitos de controlo e o BBAN.");
|
|
|
|
var country = iban[..2];
|
|
if (Lengths.TryGetValue(country, out var expectedLength) && iban.Length != expectedLength)
|
|
return ValidationResult.WrongFormat($"Um IBAN {country} tem {expectedLength} caracteres (tem {iban.Length}).");
|
|
|
|
var rearranged = Checksums.LettersToDigits(iban[4..] + iban[..4]);
|
|
if (Checksums.Mod97(rearranged) != 1)
|
|
{
|
|
var expected = FromBban(country, iban[4..])[2..4];
|
|
return ValidationResult.BadChecksum(
|
|
$"Dígitos de controlo do IBAN inválidos: esperado {expected}, encontrado {iban[2..4]}.",
|
|
["Controlo: ISO 7064 MOD 97-10"]);
|
|
}
|
|
|
|
if (country == "PT")
|
|
{
|
|
var nibResult = ValidateNib(iban[4..], "IBAN");
|
|
return nibResult with { Normalized = nibResult.IsValid ? Format(iban) : null };
|
|
}
|
|
|
|
return ValidationResult.Valid(Format(iban), $"IBAN válido ({country}).", ["Controlo: ISO 7064 MOD 97-10"]);
|
|
}
|
|
}
|