diff --git a/AyCode.Services.Tests/Nav/EkaerSubmitServiceTests.cs b/AyCode.Services.Tests/Nav/EkaerSubmitServiceTests.cs new file mode 100644 index 0000000..d3ded02 --- /dev/null +++ b/AyCode.Services.Tests/Nav/EkaerSubmitServiceTests.cs @@ -0,0 +1,122 @@ +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Text; +using AyCode.Services.Nav; +using AyCode.Services.Nav.Ekaer; +using AyCode.Services.Nav.Ekaer.Models; +using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType; + +namespace AyCode.Services.Tests.Nav; + +/// +/// Az tesztjei: a validate → send sorrend a valódi +/// fölött, fake -rel (nincs valódi hálózat) és stub validátorral. +/// +[TestClass] +public sealed class EkaerSubmitServiceTests +{ + // ---- Test doubles ------------------------------------------------------- + + private sealed class FakeHttpMessageHandler(HttpStatusCode status, string body) : HttpMessageHandler + { + public int CallCount { get; private set; } + public string? LastRequestBody { get; private set; } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + CallCount++; + if (request.Content is not null) + LastRequestBody = await request.Content.ReadAsStringAsync(cancellationToken); + return new HttpResponseMessage(status) { Content = new StringContent(body, Encoding.UTF8, "text/xml") }; + } + } + + private sealed class StubValidator(IReadOnlyList errors) : IEkaerTradeCardValidator + { + public IReadOnlyList Validate(TradeCardOperationType operation) => errors; + public IReadOnlyList Validate(IEnumerable operations) => errors; + } + + private sealed class FakeCredentials : INavCredentials + { + public string User => "u"; + public string Password => "p"; + public string SigningKey => "k"; + public string TaxNumber => "12345678"; + public string BaseUrl => "https://test.example"; + } + + // ---- Helpers ------------------------------------------------------------ + + private static EkaerSubmitService CreateSut(IReadOnlyList validationErrors, FakeHttpMessageHandler handler) + { + var manageService = new EkaerManageService(new HttpClient(handler), new FakeCredentials()); + return new EkaerSubmitService(manageService, new StubValidator(validationErrors)); + } + + private static string OkResponseXml() + => NavXmlHelper.Serialize(new ManageTradeCardsResponse { Result = new BaseResultType { FuncCode = FunctionCodeType.Ok } }); + + private static TradeCardOperationType SomeOperation() + => new() { Index = 1, Operation = OperationType.Create, TradeCard = new TradeCardType { TradeType = TradeType.I } }; + + // ---- Tesztek ------------------------------------------------------------ + + [TestMethod] + public async Task SubmitAsync_ValidationFails_DoesNotSend() + { + var handler = new FakeHttpMessageHandler(HttpStatusCode.OK, OkResponseXml()); + var sut = CreateSut([new ValidationResult("teszthiba")], handler); + + var result = await sut.SubmitAsync([SomeOperation()]); + + Assert.IsFalse(result.IsValid); + Assert.AreEqual(1, result.ValidationErrors.Count); + Assert.IsNull(result.Response); + Assert.AreEqual(0, handler.CallCount, "validációs hiba esetén NEM mehet ki kérés a NAV-nak"); + } + + [TestMethod] + public async Task SubmitAsync_Valid_SendsAndReturnsResponse() + { + var handler = new FakeHttpMessageHandler(HttpStatusCode.OK, OkResponseXml()); + var sut = CreateSut([], handler); + + var result = await sut.SubmitAsync([SomeOperation()]); + + Assert.IsTrue(result.IsValid); + Assert.AreEqual(1, handler.CallCount, "érvényes esetben egy kérés megy ki"); + Assert.IsNotNull(result.Response); + Assert.AreEqual(0, result.ValidationErrors.Count); + } + + [TestMethod] + public async Task SubmitAsync_Valid_RequestContainsOperations() + { + var handler = new FakeHttpMessageHandler(HttpStatusCode.OK, OkResponseXml()); + var sut = CreateSut([], handler); + + await sut.SubmitAsync([SomeOperation()]); + + Assert.IsNotNull(handler.LastRequestBody); + StringAssert.Contains(handler.LastRequestBody, "tradeCardOperation", "a beküldött XML tartalmazza a műveleteket"); + } + + [TestMethod] + public async Task SubmitAsync_NavHttpError_PropagatesNavReportException() + { + var handler = new FakeHttpMessageHandler(HttpStatusCode.InternalServerError, "boom"); + var sut = CreateSut([], handler); + + await Assert.ThrowsExactlyAsync(() => sut.SubmitAsync([SomeOperation()])); + } + + [TestMethod] + public async Task SubmitAsync_NullOperations_Throws() + { + var handler = new FakeHttpMessageHandler(HttpStatusCode.OK, OkResponseXml()); + var sut = CreateSut([], handler); + + await Assert.ThrowsExactlyAsync(() => sut.SubmitAsync(null!)); + } +} diff --git a/AyCode.Services.Tests/Nav/EkaerTradeCardValidatorTests.cs b/AyCode.Services.Tests/Nav/EkaerTradeCardValidatorTests.cs new file mode 100644 index 0000000..0784d9e --- /dev/null +++ b/AyCode.Services.Tests/Nav/EkaerTradeCardValidatorTests.cs @@ -0,0 +1,188 @@ +using System.ComponentModel.DataAnnotations; +using AyCode.Services.Nav.Ekaer; +using AyCode.Services.Nav.Ekaer.Models; +using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType; +using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType; + +namespace AyCode.Services.Tests.Nav; + +/// +/// Unit tesztek az -ra: séma-szintű (generált DataAnnotations) és +/// üzleti szabályok (vonó jármű, tétel, feladó/címzett) ellenőrzése, hibalista-jelleggel. A validátor +/// általános NAV/EKÁER réteg — a teszt csak az AyCode.Services-re támaszkodik (semmi projekt-specifikus). +/// +[TestClass] +public sealed class EkaerTradeCardValidatorTests +{ + private static readonly EkaerTradeCardValidator Sut = new(); + + // ---- Helpers ------------------------------------------------------------ + + /// Teljesen érvényes Create művelet, egy érvényes tétellel. A tesztek ezt „rontják el". + private static TradeCardOperationType ValidOperation() + { + var operation = new TradeCardOperationType + { + Index = 1, + Operation = OperationType.Create, + TradeCard = new TradeCardType + { + TradeType = TradeType.I, + SellerName = "Beszállító Kft", + SellerVatNumber = "12345678-2-42", + SellerCountry = "HU", + SellerAddress = "1011 Budapest Fo utca 1", + DestinationName = "FruitBank Kft", + DestinationVatNumber = "98765432-2-41", + DestinationCountry = "HU", + DestinationAddress = "1102 Budapest Raktar utca 5", + Vehicle = new BasicVehicleDetailType { PlateNumber = "ABC123", Country = "HU" }, + }, + }; + operation.TradeCard.Items.Add(new TradeCardItemType + { + ItemExternalId = "1", + TradeReason = TradeReasonType.A, + ProductVtsz = "08081010", + ProductName = "Alma", + Weight = 123.5m, + }); + return operation; + } + + private static bool HasError(IReadOnlyList errors, string fragment) + => errors.Any(e => e.ErrorMessage?.Contains(fragment, StringComparison.OrdinalIgnoreCase) == true); + + // ---- Érvényes eset ------------------------------------------------------ + + [TestMethod] + public void Validate_ValidOperation_NoErrors() + { + var errors = Sut.Validate(ValidOperation()); + Assert.AreEqual(0, errors.Count, $"érvényes művelet nem adhat hibát; kapott: {string.Join(" | ", errors.Select(e => e.ErrorMessage))}"); + } + + // ---- Üzleti szabályok --------------------------------------------------- + + [TestMethod] + public void Validate_MissingVehicle_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.Vehicle = null; + Assert.IsTrue(HasError(Sut.Validate(op), "vehicle"), "a hiányzó vonó jármű hibát ad"); + } + + [TestMethod] + public void Validate_NoItems_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.Items.Clear(); + Assert.IsTrue(HasError(Sut.Validate(op), "tétel"), "a tétel nélküli tradeCard hibát ad"); + } + + [TestMethod] + public void Validate_MissingSellerName_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.SellerName = null; + Assert.IsTrue(HasError(Sut.Validate(op), "feladó"), "hiányzó feladónév → hiba"); + } + + [TestMethod] + public void Validate_MissingDestinationName_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.DestinationName = " "; + Assert.IsTrue(HasError(Sut.Validate(op), "címzett"), "üres/whitespace címzettnév → hiba"); + } + + // ---- Séma-szint (generált DataAnnotations) ------------------------------ + + [TestMethod] + public void Validate_InvalidVtszPattern_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.Items[0].ProductVtsz = "12"; // pattern [0-9]{4,8} → túl rövid + Assert.IsTrue(HasError(Sut.Validate(op), "productVtsz"), "rossz VTSZ-formátum → séma hiba"); + } + + [TestMethod] + public void Validate_NullVtsz_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.Items[0].ProductVtsz = null; // a Required kapja el + Assert.IsTrue(HasError(Sut.Validate(op), "productVtsz"), "null VTSZ → Required hiba"); + } + + [TestMethod] + public void Validate_EmptyVtsz_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.Items[0].ProductVtsz = ""; // a séma átengedné — az üzleti szabály kapja el + Assert.IsTrue(HasError(Sut.Validate(op), "productVtsz"), "üres VTSZ → üzleti szabály hiba"); + } + + [TestMethod] + public void Validate_InvalidPlateNumber_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.Vehicle!.PlateNumber = "AB"; // MinLength 4 / pattern sérül + var errors = Sut.Validate(op); + Assert.IsTrue(HasError(errors, "plateNumber") || HasError(errors, "vehicle"), "túl rövid rendszám → séma hiba"); + } + + [TestMethod] + public void Validate_MissingProductName_ReportsError() + { + var op = ValidOperation(); + op.TradeCard!.Items[0].ProductName = null; // [Required(AllowEmptyStrings=false)] + Assert.IsTrue(HasError(Sut.Validate(op), "productName"), "hiányzó terméknév → séma hiba"); + } + + // ---- Lista / művelet-szint ---------------------------------------------- + + [TestMethod] + public void Validate_EmptyList_ReportsError() + { + var errors = Sut.Validate(new List()); + Assert.IsTrue(HasError(errors, "legalább egy"), "üres lista → hiba"); + } + + [TestMethod] + public void Validate_DeleteWithoutTradeCard_DoesNotRequireTradeCard() + { + var op = new TradeCardOperationType { Index = 1, Operation = OperationType.Delete, Tcn = "ABC123" }; + Assert.IsFalse(HasError(Sut.Validate(op), "a tradeCard kötelező"), "delete + Tcn esetén a tradeCard hiánya nem hiba"); + } + + [TestMethod] + public void Validate_NonDeleteWithoutTradeCard_ReportsError() + { + var op = new TradeCardOperationType { Index = 1, Operation = OperationType.Create, TradeCard = null }; + Assert.IsTrue(HasError(Sut.Validate(op), "tradeCard"), "create esetén a tradeCard kötelező"); + } + + // ---- Hibalista-jelleg: minden hibát összegyűjt -------------------------- + + [TestMethod] + public void Validate_CollectsAllErrors_DoesNotStopAtFirst() + { + var op = ValidOperation(); + op.TradeCard!.Vehicle = null; // üzleti + op.TradeCard.Items.Clear(); // üzleti + op.TradeCard.SellerName = null; // üzleti + op.TradeCard.DestinationVatNumber = null; // üzleti + + var errors = Sut.Validate(op); + Assert.IsTrue(errors.Count >= 4, $"minden hibát összegyűjt, nem áll meg az elsőnél; kapott {errors.Count}"); + } + + [TestMethod] + public void Validate_IndexedPath_WhenMultipleOperations() + { + var bad = ValidOperation(); + bad.TradeCard!.Vehicle = null; + var errors = Sut.Validate([ValidOperation(), bad]); + Assert.IsTrue(errors.Any(e => e.ErrorMessage?.Contains("[1]") == true), "a hibák a művelet indexével prefixeltek"); + } +} diff --git a/AyCode.Services/Nav/Ekaer/EkaerSubmitResult.cs b/AyCode.Services/Nav/Ekaer/EkaerSubmitResult.cs new file mode 100644 index 0000000..44737d2 --- /dev/null +++ b/AyCode.Services/Nav/Ekaer/EkaerSubmitResult.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using AyCode.Services.Nav.Ekaer.Models; + +namespace AyCode.Services.Nav.Ekaer; + +/// +/// Egy EKÁER beküldési kísérlet eredménye: vagy validációs hibák (ekkor NEM ment ki kérés a NAV-nak), +/// vagy a NAV-válasz (sikeres beküldés). +/// +public sealed class EkaerSubmitResult +{ + private EkaerSubmitResult(bool isValid, IReadOnlyList validationErrors, ManageTradeCardsResponse? response) + { + IsValid = isValid; + ValidationErrors = validationErrors; + Response = response; + } + + /// Igaz, ha a validáció hibátlan volt (és így a beküldés megtörtént). + public bool IsValid { get; } + + /// A validációs hibák (üres, ha érvényes volt). Ha nem üres, a bejelentés NEM lett elküldve. + public IReadOnlyList ValidationErrors { get; } + + /// A NAV-válasz, ha a beküldés megtörtént; egyébként null. + public ManageTradeCardsResponse? Response { get; } + + /// Validációs hibák miatt elutasított beküldés (nem ment ki kérés a NAV-nak). + public static EkaerSubmitResult Invalid(IReadOnlyList validationErrors) => new(false, validationErrors, null); + + /// Sikeresen elküldött beküldés, a NAV-válasszal. + public static EkaerSubmitResult Sent(ManageTradeCardsResponse response) => new(true, [], response); +} diff --git a/AyCode.Services/Nav/Ekaer/EkaerSubmitService.cs b/AyCode.Services/Nav/Ekaer/EkaerSubmitService.cs new file mode 100644 index 0000000..0b23a30 --- /dev/null +++ b/AyCode.Services/Nav/Ekaer/EkaerSubmitService.cs @@ -0,0 +1,45 @@ +using AyCode.Services.Nav.Ekaer.Models; + +namespace AyCode.Services.Nav.Ekaer; + +/// +/// +/// A NAV-küldést az végzi (auth + HTTP), a validációt az +/// . Ez az osztály csak a sorrendet (validate → send) köti egységbe. +/// +public sealed class EkaerSubmitService : IEkaerSubmitService +{ + private readonly EkaerManageService _manageService; + private readonly IEkaerTradeCardValidator _validator; + + public EkaerSubmitService(EkaerManageService manageService, IEkaerTradeCardValidator validator) + { + _manageService = manageService ?? throw new ArgumentNullException(nameof(manageService)); + _validator = validator ?? throw new ArgumentNullException(nameof(validator)); + } + + public async Task SubmitAsync(IReadOnlyList operations, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(operations); + + // 1) Validáció — bármilyen hiba esetén NEM küldünk; a teljes hibalistát visszaadjuk. + var errors = _validator.Validate(operations); + if (errors.Count > 0) + return EkaerSubmitResult.Invalid(errors); + + // 2) Request összeállítás. A Header/User példányt itt kell létrehozni (a generált ctor csak a + // TradeCardOperations kollekciót inicializálja); a TARTALMUKAT (requestId, timestamp, hash-ek) az + // ApplyAuthentication tölti a NavReportServiceBase-ben. + var request = new ManageTradeCardsRequest + { + Header = new BasicHeaderType(), + User = new UserHeaderType(), + }; + foreach (var operation in operations) + request.TradeCardOperations.Add(operation); + + // 3) Küldés. NAV-oldali hibánál NavReportException propagál (a hívó logolja/kezeli). + var response = await _manageService.ManageAsync(request, cancellationToken).ConfigureAwait(false); + return EkaerSubmitResult.Sent(response); + } +} diff --git a/AyCode.Services/Nav/Ekaer/EkaerTradeCardValidator.cs b/AyCode.Services/Nav/Ekaer/EkaerTradeCardValidator.cs new file mode 100644 index 0000000..bbacb86 --- /dev/null +++ b/AyCode.Services/Nav/Ekaer/EkaerTradeCardValidator.cs @@ -0,0 +1,131 @@ +using System.ComponentModel.DataAnnotations; +using AyCode.Services.Nav.Ekaer.Models; + +namespace AyCode.Services.Nav.Ekaer; + +/// +/// +/// Két forrásból gyűjt hibát: +/// +/// a generált modellek -jai (Required / RegularExpression / Min-MaxLength), +/// rekurzívan a tradeCard + tételek + jármű + helyszínek fölött — ezt a végzi; +/// 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. +/// +/// +public sealed class EkaerTradeCardValidator : IEkaerTradeCardValidator +{ + public IReadOnlyList Validate(TradeCardOperationType operation) + { + ArgumentNullException.ThrowIfNull(operation); + var errors = new List(); + + ValidateOperation(operation, "tradeCardOperation", errors); + return errors; + } + + public IReadOnlyList Validate(IEnumerable operations) + { + ArgumentNullException.ThrowIfNull(operations); + var list = operations.ToList(); + var errors = new List(); + + 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 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 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); + } + + /// + /// A generált modell -jainak ellenőrzése (Required / pattern / hossz), + /// property-szelektíven. A generátor a nullable érték-mezőkhöz *Value + *Specified segéd-property-ket + /// készít, és a tényleges attribútumok ezeken ülnek; a *Value 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 *Specified + /// flag alapján dönt a kiírásról, így a default *Value sosem kerül ki a beküldött XML-be. + /// + private static void ValidateAnnotations(object instance, string path, List 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(); + + 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 errors) + { + if (string.IsNullOrWhiteSpace(value)) + errors.Add(Error(path, message)); + } + + private static ValidationResult Error(string path, string message) => new($"{path}: {message}"); +} diff --git a/AyCode.Services/Nav/Ekaer/IEkaerSubmitService.cs b/AyCode.Services/Nav/Ekaer/IEkaerSubmitService.cs new file mode 100644 index 0000000..708ba3c --- /dev/null +++ b/AyCode.Services/Nav/Ekaer/IEkaerSubmitService.cs @@ -0,0 +1,17 @@ +using AyCode.Services.Nav.Ekaer.Models; + +namespace AyCode.Services.Nav.Ekaer; + +/// +/// Általános EKÁER beküldés-orchestráció: validál (a generált tradeCard-okat a NAV-szabályok ellen), és csak +/// hibátlan esetben küld. Nem ismer projekt-specifikus (pl. FruitBank) típust — a már leképzett műveleteket kapja. +/// +public interface IEkaerSubmitService +{ + /// + /// Validálja, majd — ha hibátlan — elküldi a tradeCard műveleteket a NAV-nak. + /// Validációs hiba esetén (nem megy ki kérés); + /// NAV-oldali hiba esetén NavReportException propagál. + /// + Task SubmitAsync(IReadOnlyList operations, CancellationToken cancellationToken = default); +} diff --git a/AyCode.Services/Nav/Ekaer/IEkaerTradeCardValidator.cs b/AyCode.Services/Nav/Ekaer/IEkaerTradeCardValidator.cs new file mode 100644 index 0000000..3db167c --- /dev/null +++ b/AyCode.Services/Nav/Ekaer/IEkaerTradeCardValidator.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using AyCode.Services.Nav.Ekaer.Models; + +namespace AyCode.Services.Nav.Ekaer; + +/// +/// Az EKÁER tradeCard műveletek beküldés-előtti ellenőrzése. A NAV-séma (a generált modellek +/// -jai: Required / pattern / hossz) ÉS az XSD-n felüli üzleti szabályok +/// (pl. a vonó jármű kötelező) ellenőrzése. Minden hibát összegyűjt — nem az elsőnél áll meg. +/// +/// +/// Általános NAV/EKÁER réteg — nem ismer projekt-specifikus (pl. FruitBank) típust, csak a generált modelleket. +/// A hívó (pl. a szerver-oldali EKÁER-service) a beküldés ELŐTT hívja: ha a visszaadott lista nem üres, +/// a bejelentés NEM küldhető. A NAV-validációs szabályok kivonata: Nav/docs/EKAER_VALIDATION.md. +/// +public interface IEkaerTradeCardValidator +{ + /// Egyetlen művelet ellenőrzése. Üres lista = érvényes. + IReadOnlyList Validate(TradeCardOperationType operation); + + /// Több művelet ellenőrzése; a hibák a művelet indexével prefixelve. Üres lista = mind érvényes. + IReadOnlyList Validate(IEnumerable operations); +}