- 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
2.7 KiB
C#
76 lines
2.7 KiB
C#
using GeradoresService.Core;
|
||
|
||
namespace GeradoresService.Generators.Financeiro;
|
||
|
||
/// <summary>
|
||
/// NIB: banco (4) + balcão (4) + conta (11) + controlo (2). Controlo = 98 - (NIB19 × 100 mod 97).
|
||
/// </summary>
|
||
public class NibGenerator : GeneratorBase
|
||
{
|
||
public static readonly IReadOnlyList<(string Code, string Name)> Banks =
|
||
[
|
||
("0007", "Novo Banco"),
|
||
("0010", "BPI"),
|
||
("0018", "Santander Totta"),
|
||
("0023", "Banco ActivoBank"),
|
||
("0033", "Millennium BCP"),
|
||
("0035", "Caixa Geral de Depósitos"),
|
||
("0036", "Banco Montepio"),
|
||
("0045", "Crédito Agrícola"),
|
||
("0079", "Banco BIC"),
|
||
("0193", "Banco CTT"),
|
||
("0269", "Bankinter"),
|
||
];
|
||
|
||
public override string Id => "nib";
|
||
public override string Name => "NIB";
|
||
public override string Category => Categories.Financeiro;
|
||
public override string Description => "Número de Identificação Bancária (21 dígitos).";
|
||
|
||
public override IReadOnlyList<GeneratorOption> Options =>
|
||
[
|
||
Option("banco", "Banco", "qualquer",
|
||
[("qualquer", "Qualquer"), .. Banks.Select(b => (b.Code, $"{b.Code} — {b.Name}"))]),
|
||
];
|
||
|
||
public override GeneratedItem Generate(GeneratorContext context) => new(BuildNib(context));
|
||
|
||
protected string BuildNib(GeneratorContext context)
|
||
{
|
||
var bank = context.Option(Options[0]);
|
||
if (bank == "qualquer")
|
||
bank = context.Pick(Banks).Code;
|
||
var body = bank + context.Digits(4) + context.Digits(11);
|
||
return body + NibCheck(body);
|
||
}
|
||
|
||
public static string NibCheck(string first19) =>
|
||
(98 - Checksums.Mod97(first19 + "00")).ToString("D2");
|
||
|
||
public static string? BankName(string nib) =>
|
||
Banks.FirstOrDefault(b => nib.StartsWith(b.Code)).Name;
|
||
|
||
public override ValidationResult Validate(string value)
|
||
{
|
||
var nib = Checksums.Clean(value);
|
||
if (nib.Length != 21 || !Checksums.AllDigits(nib))
|
||
return ValidationResult.WrongFormat("Um NIB tem 21 dígitos.");
|
||
return ValidateNib(nib, "NIB");
|
||
}
|
||
|
||
public static ValidationResult ValidateNib(string nib, string label)
|
||
{
|
||
var expected = NibCheck(nib[..19]);
|
||
var details = new List<string>
|
||
{
|
||
$"Banco: {nib[..4]}{(BankName(nib) is { } n ? $" ({n})" : "")}",
|
||
$"Balcão: {nib[4..8]}",
|
||
$"Conta: {nib[8..19]}",
|
||
$"Dígitos de controlo esperados: {expected} (módulo 97)",
|
||
};
|
||
return nib[19..] == expected
|
||
? ValidationResult.Valid(nib, $"{label} válido.", details)
|
||
: ValidationResult.BadChecksum($"Dígitos de controlo do NIB inválidos: esperado {expected}, encontrado {nib[19..]}.", details);
|
||
}
|
||
}
|