- 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.4 KiB
C#
76 lines
3.4 KiB
C#
using System.Globalization;
|
||
using System.Text.RegularExpressions;
|
||
using GeradoresService.Core;
|
||
|
||
namespace GeradoresService.Generators.Financeiro;
|
||
|
||
/// <summary>
|
||
/// Referência Multibanco: entidade (5) + referência (9) + montante.
|
||
/// Os 2 últimos dígitos da referência são controlo: 98 - (Σ dígito × peso mod 97),
|
||
/// calculado sobre entidade(5) + referência(7) + montante em cêntimos(8).
|
||
/// </summary>
|
||
public sealed partial class ReferenciaMultibancoGenerator : GeneratorBase
|
||
{
|
||
private static readonly int[] Weights = [51, 73, 17, 89, 38, 62, 45, 53, 15, 50, 5, 49, 34, 81, 76, 27, 90, 9, 30, 3];
|
||
|
||
public override string Id => "multibanco";
|
||
public override string Name => "Referência Multibanco";
|
||
public override string Category => Categories.Financeiro;
|
||
public override string Description => "Entidade, referência e montante com dígitos de controlo válidos.";
|
||
|
||
public override GeneratedItem Generate(GeneratorContext context)
|
||
{
|
||
var entity = (10000 + context.Random.Next(90000)).ToString();
|
||
var cents = 100 + context.Random.Next(50000);
|
||
var reference = context.Digits(7);
|
||
reference += CheckDigits(entity, reference, cents);
|
||
|
||
var amount = (cents / 100m).ToString("0.00", CultureInfo.InvariantCulture);
|
||
return new GeneratedItem($"{entity} {FormatReference(reference)} {amount}", new Dictionary<string, string>
|
||
{
|
||
["Entidade"] = entity,
|
||
["Referência"] = FormatReference(reference),
|
||
["Montante"] = $"{amount} €",
|
||
});
|
||
}
|
||
|
||
public static string CheckDigits(string entity, string reference7, long cents)
|
||
{
|
||
var chk = entity + reference7 + cents.ToString("D8");
|
||
var sum = 0;
|
||
for (var i = 0; i < 20; i++)
|
||
sum += (chk[i] - '0') * Weights[i];
|
||
return (98 - sum % 97).ToString("D2");
|
||
}
|
||
|
||
private static string FormatReference(string r) => $"{r[..3]} {r[3..6]} {r[6..]}";
|
||
|
||
public override ValidationResult Validate(string value)
|
||
{
|
||
var m = Pattern().Match(value.Trim());
|
||
if (!m.Success)
|
||
return ValidationResult.WrongFormat("Formato esperado: entidade (5 dígitos), referência (9 dígitos) e montante, ex.: 12345 123 456 789 10.00");
|
||
|
||
var entity = m.Groups["ent"].Value;
|
||
var reference = Checksums.Clean(m.Groups["ref"].Value);
|
||
var amount = decimal.Parse(m.Groups["val"].Value.Replace(',', '.'), CultureInfo.InvariantCulture);
|
||
var cents = (long)Math.Round(amount * 100);
|
||
if (cents > 99_999_999)
|
||
return ValidationResult.WrongFormat("Montante demasiado elevado.");
|
||
|
||
var expected = CheckDigits(entity, reference[..7], cents);
|
||
var details = new List<string>
|
||
{
|
||
$"Entidade: {entity}",
|
||
$"Montante: {amount.ToString("0.00", CultureInfo.InvariantCulture)} €",
|
||
$"Dígitos de controlo esperados: {expected}",
|
||
};
|
||
return reference[7..] == expected
|
||
? ValidationResult.Valid($"{entity} {FormatReference(reference)} {amount.ToString("0.00", CultureInfo.InvariantCulture)}", "Referência Multibanco válida.", details)
|
||
: ValidationResult.BadChecksum($"Dígitos de controlo inválidos: esperado {expected}, encontrado {reference[7..]}.", details);
|
||
}
|
||
|
||
[GeneratedRegex(@"^(?<ent>\d{5})[\s|;]+(?<ref>\d{3}\s?\d{3}\s?\d{3})[\s|;]+(?<val>\d{1,6}(?:[.,]\d{1,2})?)\s*€?$")]
|
||
private static partial Regex Pattern();
|
||
}
|