Files
Geradores/GeradoresService/Generators/Financeiro/ReferenciaMultibancoGenerator.cs
Marco Santos d7ea8a9b9e 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>
2026-10-11 23:51:39 +01:00

76 lines
3.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}