132 lines
6.8 KiB
C#
132 lines
6.8 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using AyCode.Services.Nav.Ekaer.Models;
|
|
|
|
namespace AyCode.Services.Nav.Ekaer;
|
|
|
|
/// <inheritdoc cref="IEkaerTradeCardValidator"/>
|
|
/// <remarks>
|
|
/// Két forrásból gyűjt hibát:
|
|
/// <list type="number">
|
|
/// <item>a generált modellek <see cref="ValidationAttribute"/>-jai (Required / RegularExpression / Min-MaxLength),
|
|
/// rekurzívan a tradeCard + tételek + jármű + helyszínek fölött — ezt a <see cref="Validator"/> végzi;</item>
|
|
/// <item>az XSD-ben NEM jelölt, de a NAV által megkövetelt üzleti szabályok (vonó jármű, legalább egy tétel,
|
|
/// feladó/címzett alapadatok) — ezeket kézzel ellenőrizzük.</item>
|
|
/// </list>
|
|
/// </remarks>
|
|
public sealed class EkaerTradeCardValidator : IEkaerTradeCardValidator
|
|
{
|
|
public IReadOnlyList<ValidationResult> Validate(TradeCardOperationType operation)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(operation);
|
|
var errors = new List<ValidationResult>();
|
|
|
|
ValidateOperation(operation, "tradeCardOperation", errors);
|
|
return errors;
|
|
}
|
|
|
|
public IReadOnlyList<ValidationResult> Validate(IEnumerable<TradeCardOperationType> operations)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(operations);
|
|
var list = operations.ToList();
|
|
var errors = new List<ValidationResult>();
|
|
|
|
if (list.Count == 0)
|
|
{
|
|
errors.Add(Error("tradeCardOperations", "legalább egy tradeCard művelet szükséges"));
|
|
return errors;
|
|
}
|
|
|
|
for (var i = 0; i < list.Count; i++)
|
|
ValidateOperation(list[i], $"tradeCardOperations[{i}]", errors);
|
|
|
|
return errors;
|
|
}
|
|
|
|
private static void ValidateOperation(TradeCardOperationType operation, string path, List<ValidationResult> errors)
|
|
{
|
|
// A művelet saját attribútumai (index, operation [Required]).
|
|
ValidateAnnotations(operation, path, errors);
|
|
|
|
var tradeCard = operation.TradeCard;
|
|
if (tradeCard is null)
|
|
{
|
|
// delete művelethez elég a Tcn; minden más művelethez a tradeCard kötelező.
|
|
if (operation.Operation != OperationType.Delete)
|
|
errors.Add(Error($"{path}.tradeCard", "a tradeCard kötelező (kivéve delete művelet, ahol a Tcn elég)"));
|
|
return;
|
|
}
|
|
|
|
ValidateTradeCard(tradeCard, $"{path}.tradeCard", errors);
|
|
}
|
|
|
|
private static void ValidateTradeCard(TradeCardType tradeCard, string path, List<ValidationResult> errors)
|
|
{
|
|
// 1) Séma-szint: a generált DataAnnotations a tradeCard saját + örökölt property-jein.
|
|
ValidateAnnotations(tradeCard, path, errors);
|
|
|
|
// 2) Séma-szint a beágyazott objektumokon (a Validator nem rekurzív, ezért kézzel járjuk be).
|
|
if (tradeCard.Vehicle is not null) ValidateAnnotations(tradeCard.Vehicle, $"{path}.vehicle", errors);
|
|
if (tradeCard.Vehicle2 is not null) ValidateAnnotations(tradeCard.Vehicle2, $"{path}.vehicle2", errors);
|
|
if (tradeCard.LoadLocation is not null) ValidateAnnotations(tradeCard.LoadLocation, $"{path}.loadLocation", errors);
|
|
if (tradeCard.UnloadLocation is not null) ValidateAnnotations(tradeCard.UnloadLocation, $"{path}.unloadLocation", errors);
|
|
|
|
for (var i = 0; i < tradeCard.Items.Count; i++)
|
|
{
|
|
var itemPath = $"{path}.items[{i}]";
|
|
var item = tradeCard.Items[i];
|
|
|
|
ValidateAnnotations(item, itemPath, errors);
|
|
|
|
// A productVtsz a modellen [Required(AllowEmptyStrings=true)] + pattern — az üres string ("") elcsúszna
|
|
// a sémán (a null bukik a Required-en, az "" nem, mert a RegularExpression az üreset validnak veszi),
|
|
// ezért üzleti szabállyal lezárjuk. A { Length: 0 } property-pattern a null-ra NEM illeszkedik → nincs dupla hiba.
|
|
if (item.ProductVtsz is { Length: 0 }) errors.Add(Error($"{itemPath}.productVtsz", "a tétel VTSZ-száma (productVtsz) nem lehet üres"));
|
|
}
|
|
|
|
// 3) Üzleti szabályok (az XSD nem jelöli [Required]-ként, de a NAV megköveteli).
|
|
if (tradeCard.Vehicle is null) errors.Add(Error($"{path}.vehicle", "a vonó jármű (rendszám) kötelező — rendszám nélkül a NAV elutasítja a bejelentést"));
|
|
if (tradeCard.Items.Count == 0) errors.Add(Error($"{path}.items", "legalább egy tétel szükséges"));
|
|
|
|
RequireText(tradeCard.SellerName, $"{path}.sellerName", "a feladó (eladó) neve kötelező", errors);
|
|
RequireText(tradeCard.SellerVatNumber, $"{path}.sellerVatNumber", "a feladó adószáma kötelező", errors);
|
|
RequireText(tradeCard.SellerCountry, $"{path}.sellerCountry", "a feladó országkódja kötelező", errors);
|
|
RequireText(tradeCard.DestinationName, $"{path}.destinationName", "a címzett neve kötelező", errors);
|
|
RequireText(tradeCard.DestinationVatNumber, $"{path}.destinationVatNumber", "a címzett adószáma kötelező", errors);
|
|
RequireText(tradeCard.DestinationCountry, $"{path}.destinationCountry", "a címzett országkódja kötelező", errors);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A generált modell <see cref="ValidationAttribute"/>-jainak ellenőrzése (Required / pattern / hossz),
|
|
/// property-szelektíven. A generátor a nullable érték-mezőkhöz <c>*Value</c> + <c>*Specified</c> segéd-property-ket
|
|
/// készít, és a tényleges attribútumok ezeken ülnek; a <c>*Value</c> default értéke (pl. a 0001-01-01 DateTime a
|
|
/// timestamp-pattern ellen) téves hibát adna, ezért kihagyjuk őket — a serializáció úgyis a <c>*Specified</c>
|
|
/// flag alapján dönt a kiírásról, így a default <c>*Value</c> sosem kerül ki a beküldött XML-be.
|
|
/// </summary>
|
|
private static void ValidateAnnotations(object instance, string path, List<ValidationResult> errors)
|
|
{
|
|
foreach (var property in instance.GetType().GetProperties())
|
|
{
|
|
if (property.GetIndexParameters().Length != 0) continue; // indexer kihagyása
|
|
if (property.Name.EndsWith("Value", StringComparison.Ordinal) ||
|
|
property.Name.EndsWith("Specified", StringComparison.Ordinal))
|
|
continue;
|
|
|
|
var context = new ValidationContext(instance) { MemberName = property.Name };
|
|
var local = new List<ValidationResult>();
|
|
|
|
Validator.TryValidateProperty(property.GetValue(instance), context, local);
|
|
|
|
foreach (var result in local)
|
|
errors.Add(new ValidationResult($"{path}.{property.Name}: {result.ErrorMessage}", result.MemberNames));
|
|
}
|
|
}
|
|
|
|
private static void RequireText(string? value, string path, string message, List<ValidationResult> errors)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
errors.Add(Error(path, message));
|
|
}
|
|
|
|
private static ValidationResult Error(string path, string message) => new($"{path}: {message}");
|
|
}
|