FruitBankHybridApp/FruitBank.Common/Services/Ekaer/EkaerReportability.cs

84 lines
5.0 KiB
C#

using AyCode.Services.Nav.Ekaer;
namespace FruitBank.Common.Services.Ekaer;
/// <summary>Egy szállítmány EKÁER bejelentés-kötelezettsége.</summary>
public enum EkaerObligation
{
/// <summary>Kötelező bejelenteni (sort kell létrehozni).</summary>
Required,
/// <summary>Nem kötelező (belföld, küszöb alatt) — NEM hiba, nincs üzenet.</summary>
NotRequired,
/// <summary>A döntés nem hozható meg adathiány/formátumhiba miatt (pl. érvénytelen országkód) — NINCS sor, az okok az üzenetekben.</summary>
DataError,
}
/// <summary>A kötelezettség-kiértékelés eredménye: a döntés + (csak DataError esetén) a felhasználónak szóló okok.</summary>
public sealed class EkaerObligationResult
{
public EkaerObligation Obligation { get; private init; }
public IReadOnlyList<string> Errors { get; private init; } = [];
public static EkaerObligationResult Required { get; } = new() { Obligation = EkaerObligation.Required };
public static EkaerObligationResult NotRequired { get; } = new() { Obligation = EkaerObligation.NotRequired };
public static EkaerObligationResult DataError(IReadOnlyList<string> errors) =>
new() { Obligation = EkaerObligation.DataError, Errors = errors };
}
/// <summary>
/// EKÁER bejelentés-kötelezettség eldöntése egy normalizált <see cref="EkaerConsignment"/>-re — KÖZÖS a bejövő
/// (document-csoport) és a kimenő (order) ágon. A NAV mindkét irányban büntet (a felesleges bejelentés ÉS a kimaradás
/// is bírság), ezért: a bizonytalan adatot (érvénytelen országkód) NEM döntjük el magától → <see cref="EkaerObligation.DataError"/>.
/// </summary>
/// <remarks>
/// Sorrend: (1) az AGGREGÁLT tömeg/érték küszöb FELETT → Required, az országkódtól FÜGGETLENÜL — belföld+küszöb
/// felett és külföld egyaránt kötelező; az országkód-hibát ilyenkor a generate-validálás jelzi, a sor létrejön;
/// (2) küszöb ALATT → itt számít az ország: hiányzó/érvénytelen országkód → DataError (a foreign-vs-belföld nem
/// dönthető el); (3) küszöb alatt, érvényes országkódok: külföld (eltér) → Required, belföld (egyezik) → NotRequired.
/// A 13/2020. (XII. 23.) PM rendelet alapján a küszöb feladó→címzett→jármű relációra aggregált — a hívó már
/// így állítja össze a <see cref="EkaerConsignment.Lines"/>-t (bejövőnél a (Shipping, Partner) csoport tételei).
/// </remarks>
public static class EkaerReportability
{
public static EkaerObligationResult Evaluate(EkaerConsignment consignment, IEkaerSettings settings)
{
ArgumentNullException.ThrowIfNull(consignment);
ArgumentNullException.ThrowIfNull(settings);
// (1) Küszöb FELETT → kötelező, FÜGGETLENÜL az országkódtól: belföld+küszöb felett ÉS külföld egyaránt kötelező.
// Az országkód-hibát ilyenkor NEM itt blokkoljuk — a sor létrejön, a hibát a generate-validálás jelzi.
var totalWeight = consignment.Lines.Sum(l => l.WeightKg);
var totalValueHuf = consignment.Lines.Sum(l => l.ValueHuf ?? 0L);
if (totalWeight >= settings.ThresholdWeightKg || totalValueHuf >= settings.ThresholdValueHuf)
return EkaerObligationResult.Required;
// (2) Küszöb ALATT: itt MÁR az ország dönt — csak a külföldi (eltérő országkód) reláció kötelező. Ehhez érvényes
// ISO-2 országkódok kellenek; hiányzó/érvénytelen → a foreign-vs-belföld nem dönthető el → DataError.
var subject = Subject(consignment);
var errors = new List<string>();
if (!IsValidCountry(consignment.Seller.CountryCode))
errors.Add($"{subject}: a feladó országkódja hiányzik vagy érvénytelen ('{consignment.Seller.CountryCode}').");
if (!IsValidCountry(consignment.Buyer.CountryCode))
errors.Add($"{subject}: a címzett országkódja hiányzik vagy érvénytelen ('{consignment.Buyer.CountryCode}').");
if (errors.Count > 0)
return EkaerObligationResult.DataError(errors);
// (3) Küszöb alatt, érvényes országkódok: külföld (eltér) → kötelező, belföld (egyezik) → nem kötelező (üzenet nélkül).
return CountryEquals(consignment.Seller.CountryCode, consignment.Buyer.CountryCode)
? EkaerObligationResult.NotRequired
: EkaerObligationResult.Required;
}
/// <summary>Érvényes EKÁER országkód: pontosan 2 ASCII betű (ISO-2). Üres / más hosszúságú → érvénytelen.</summary>
private static bool IsValidCountry(string? code)
{
var c = code?.Trim();
return c is { Length: 2 } && c.All(char.IsAsciiLetter);
}
private static bool CountryEquals(string? a, string? b) => string.Equals(a?.Trim(), b?.Trim(), StringComparison.OrdinalIgnoreCase);
private static string Subject(EkaerConsignment c)
=> c.IsOutgoing ? $"Rendelés #{c.ForeignKey}" : string.IsNullOrWhiteSpace(c.Seller.Name) ? $"Szállítólevél #{c.ForeignKey}" : c.Seller.Name!;
}