- 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>
65 lines
2.9 KiB
C#
65 lines
2.9 KiB
C#
using System.Text.RegularExpressions;
|
||
using GeradoresService.Core;
|
||
|
||
namespace GeradoresService.Generators.Outros;
|
||
|
||
/// <summary>Matrículas portuguesas em todos os formatos desde 1937. Apenas formato.</summary>
|
||
public sealed partial class MatriculaGenerator : GeneratorBase
|
||
{
|
||
// K, W e Y não eram usadas nas matrículas portuguesas.
|
||
private const string Letters = "ABCDEFGHIJLMNOPQRSTUVXZ";
|
||
|
||
private sealed record Format(string Key, string Label, string Mask, string Period);
|
||
|
||
private static readonly Format[] Formats =
|
||
[
|
||
new("2020", "AA-00-AA (desde 2020)", "LL-DD-LL", "2020 – actualidade"),
|
||
new("2005", "00-AA-00 (2005–2020)", "DD-LL-DD", "2005 – 2020"),
|
||
new("1992", "00-00-AA (1992–2005)", "DD-DD-LL", "1992 – 2005"),
|
||
new("1937", "AA-00-00 (1937–1992)", "LL-DD-DD", "1937 – 1992"),
|
||
];
|
||
|
||
public override string Id => "matricula";
|
||
public override string Name => "Matrícula";
|
||
public override string Category => Categories.Outros;
|
||
public override string Description => "Matrícula automóvel portuguesa em qualquer um dos formatos.";
|
||
public override bool HasChecksum => false;
|
||
|
||
public override IReadOnlyList<GeneratorOption> Options =>
|
||
[
|
||
Option("formato", "Formato", "2020", [.. Formats.Select(f => (f.Key, f.Label)), ("qualquer", "Qualquer")]),
|
||
];
|
||
|
||
public override GeneratedItem Generate(GeneratorContext context)
|
||
{
|
||
var key = context.Option(Options[0]);
|
||
var format = key == "qualquer" ? context.Pick(Formats) : Formats.Single(f => f.Key == key);
|
||
var plate = string.Concat(format.Mask.Select(c => c switch
|
||
{
|
||
'L' => Letters[context.Random.Next(Letters.Length)],
|
||
'D' => (char)('0' + context.Random.Next(10)),
|
||
_ => c,
|
||
}));
|
||
return new GeneratedItem(plate);
|
||
}
|
||
|
||
public override ValidationResult Validate(string value)
|
||
{
|
||
var plate = Checksums.Clean(value).ToUpperInvariant();
|
||
if (plate.Length != 6 || !plate.All(char.IsAsciiLetterOrDigit))
|
||
return ValidationResult.WrongFormat("Uma matrícula tem 6 caracteres em 3 pares (ex.: AA-00-AA).");
|
||
|
||
var mask = string.Concat(plate.Chunk(2).Select(p => p.All(char.IsAsciiDigit) ? "DD" : p.All(char.IsAsciiLetter) ? "LL" : "??"));
|
||
var masked = $"{mask[..2]}-{mask[2..4]}-{mask[4..]}";
|
||
var format = Formats.FirstOrDefault(f => f.Mask == masked);
|
||
if (format is null)
|
||
return ValidationResult.WrongFormat("Não corresponde a nenhum formato de matrícula portuguesa.");
|
||
|
||
var formatted = $"{plate[..2]}-{plate[2..4]}-{plate[4..]}";
|
||
if (plate.Any(c => c is 'K' or 'W' or 'Y'))
|
||
return ValidationResult.BadChecksum("As letras K, W e Y não são usadas em matrículas portuguesas.");
|
||
|
||
return ValidationResult.Valid(formatted, $"Matrícula válida (formato {format.Period}).", [$"Formato: {format.Label}"]);
|
||
}
|
||
}
|