Reescrita dos geradores: biblioteca IGenerator, minimal API e novo frontend
- 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>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
using GeradoresService.Core;
|
||||
|
||||
namespace GeradoresService.Generators.Identificacao;
|
||||
|
||||
/// <summary>
|
||||
/// Número de documento do Cartão de Cidadão: NNNNNNNN C VV T
|
||||
/// - NNNNNNNN: número de identificação civil (NIC)
|
||||
/// - C: dígito de controlo do NIC (módulo 11)
|
||||
/// - VV: versão (duas letras/dígitos)
|
||||
/// - T: dígito de controlo do documento completo (Luhn com A=10..Z=35), conforme especificação da AMA.
|
||||
/// </summary>
|
||||
public sealed class CartaoCidadaoGenerator : GeneratorBase
|
||||
{
|
||||
public override string Id => "cc";
|
||||
public override string Name => "Cartão de Cidadão";
|
||||
public override string Category => Categories.Identificacao;
|
||||
public override string Description => "Número de documento do Cartão de Cidadão.";
|
||||
|
||||
public override IReadOnlyList<GeneratorOption> Options =>
|
||||
[
|
||||
Option("formato", "Formato", "espacos", ("espacos", "12345678 9 ZZ1"), ("compacto", "123456789ZZ1")),
|
||||
];
|
||||
|
||||
public override GeneratedItem Generate(GeneratorContext context)
|
||||
{
|
||||
var nic = context.Digits(8);
|
||||
var c = Checksums.Mod11(nic);
|
||||
// As versões começam em ZZ e vão decrementando.
|
||||
var version = "Z" + (char)('A' + context.Random.Next(26));
|
||||
var payload = $"{nic}{c}{version}";
|
||||
var t = Checksums.LuhnCheckDigit(payload);
|
||||
var compact = payload + t;
|
||||
return new GeneratedItem(context.Option(Options[0]) == "compacto" ? compact : Format(compact));
|
||||
}
|
||||
|
||||
private static string Format(string compact) => $"{compact[..8]} {compact[8]} {compact[9..]}";
|
||||
|
||||
public override ValidationResult Validate(string value)
|
||||
{
|
||||
var cc = Checksums.Clean(value).ToUpperInvariant();
|
||||
if (cc.Length != 12
|
||||
|| !Checksums.AllDigits(cc[..9])
|
||||
|| !cc[9..11].All(char.IsAsciiLetterOrDigit)
|
||||
|| !char.IsAsciiDigit(cc[11]))
|
||||
return ValidationResult.WrongFormat("Formato esperado: 8 dígitos, 1 dígito, 2 letras/dígitos de versão e 1 dígito (ex.: 00000000 0 ZZ4).");
|
||||
|
||||
var details = new List<string>
|
||||
{
|
||||
$"NIC: {cc[..8]}",
|
||||
$"Versão: {cc[9..11]}",
|
||||
"Controlo: algoritmo Luhn sobre os 12 caracteres (A=10 … Z=35)",
|
||||
};
|
||||
|
||||
if (!Checksums.LuhnValid(cc))
|
||||
{
|
||||
var expected = Checksums.LuhnCheckDigit(cc[..11]);
|
||||
return ValidationResult.BadChecksum($"Dígito de controlo inválido: esperado {expected}, encontrado {cc[11]}.", details);
|
||||
}
|
||||
return ValidationResult.Valid(Format(cc), "Número de documento do CC válido.", details);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user