- 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>
62 lines
2.5 KiB
C#
62 lines
2.5 KiB
C#
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);
|
|
}
|
|
}
|