diff --git a/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs b/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs
index 616cb277..c5384fb4 100644
--- a/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs
+++ b/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs
@@ -1,3 +1,4 @@
+using AyCode.Services.Nav;
using AyCode.Services.Nav.Ekaer;
using AyCode.Services.Nav.Ekaer.Models;
using FruitBank.Common.Entities;
@@ -15,12 +16,14 @@ public sealed class FruitBankEkaerService : IFruitBankEkaerService
{
private readonly IShippingToEkaerMapper _mapper;
private readonly IEkaerSubmitService _submitService;
+ private readonly IEkaerTradeCardValidator _validator;
private readonly EkaerCompanyInfo _company;
- public FruitBankEkaerService(IShippingToEkaerMapper mapper, IEkaerSubmitService submitService, EkaerCompanyInfo company)
+ public FruitBankEkaerService(IShippingToEkaerMapper mapper, IEkaerSubmitService submitService, IEkaerTradeCardValidator validator, EkaerCompanyInfo company)
{
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_submitService = submitService ?? throw new ArgumentNullException(nameof(submitService));
+ _validator = validator ?? throw new ArgumentNullException(nameof(validator));
_company = company ?? throw new ArgumentNullException(nameof(company));
}
@@ -32,4 +35,27 @@ public sealed class FruitBankEkaerService : IFruitBankEkaerService
var operations = _mapper.MapShipping(shipping, _company, operation);
return _submitService.SubmitAsync(operations, cancellationToken);
}
+
+ public EkaerHistory GenerateEkaerXmlDocument(ShippingDocument document, EkaerHistory? ekaerHistory = null)
+ {
+ ArgumentNullException.ThrowIfNull(document);
+
+ ekaerHistory ??= new EkaerHistory { ForeignKey = document.Id, IsOutgoing = false };
+
+ var operation = new TradeCardOperationType
+ {
+ Index = 1,
+ Operation = OperationType.Create,
+ TradeCard = _mapper.MapDocument(document, _company),
+ };
+
+ var errors = _validator.Validate(operation);
+
+ // Az XML validációs hibánál IS tárolódik — a detail-nézetben így látszik, mi hiányzik.
+ ekaerHistory.XmlDoc = NavXmlHelper.Serialize(operation.TradeCard);
+ ekaerHistory.Status = errors.Count == 0 ? EkaerStatus.Generated : EkaerStatus.ValidationError;
+ ekaerHistory.ErrorText = errors.Count == 0 ? null : string.Join(Environment.NewLine, errors.Select(e => e.ErrorMessage));
+
+ return ekaerHistory;
+ }
}
diff --git a/FruitBank.Common.Server/Services/Ekaer/IFruitBankEkaerService.cs b/FruitBank.Common.Server/Services/Ekaer/IFruitBankEkaerService.cs
index d9f13b9c..244bbf3a 100644
--- a/FruitBank.Common.Server/Services/Ekaer/IFruitBankEkaerService.cs
+++ b/FruitBank.Common.Server/Services/Ekaer/IFruitBankEkaerService.cs
@@ -16,4 +16,14 @@ public interface IFruitBankEkaerService
/// vagy a NAV-válasz — lásd .
///
Task SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default);
+
+ ///
+ /// Egy szállítólevélből legenerálja az EKÁER tradeCard XML-t és validálja — a (meglévő vagy új)
+ /// rekordot tölti: XmlDoc + Status
+ /// ( / ) + ErrorText.
+ /// NEM perzisztál és NEM hív NAV-ot — a mentés (upsert) a hívó SignalR endpoint dolga.
+ ///
+ /// A szállítólevél a betöltött gráffal (Partner, Items+ProductDto, Shipping→járművek).
+ /// A dokumentum meglévő rekordja (újrageneráláskor); null → új rekord készül.
+ EkaerHistory GenerateEkaerXmlDocument(ShippingDocument document, EkaerHistory? ekaerHistory = null);
}
diff --git a/FruitBank.Common/Interfaces/IFruitBankDataControllerCommon.cs b/FruitBank.Common/Interfaces/IFruitBankDataControllerCommon.cs
index 30c81db2..9e31b37c 100644
--- a/FruitBank.Common/Interfaces/IFruitBankDataControllerCommon.cs
+++ b/FruitBank.Common/Interfaces/IFruitBankDataControllerCommon.cs
@@ -34,6 +34,8 @@ public interface IFruitBankDataControllerCommon
public Task?> GetEkaerHistoriesByForeignKey(int foreignKey);
public Task AddEkaerHistory(EkaerHistory ekaerHistory);
public Task UpdateEkaerHistory(EkaerHistory ekaerHistory);
+ public Task GenerateEkaerXmlDocument(int shippingDocumentId);
+ public Task CreateEkaerHistory(int foreignKey, bool isOutgoing);
#endregion EkaerHistory
#region CargoPartner
diff --git a/FruitBank.Common/Services/Ekaer/IShippingToEkaerMapper.cs b/FruitBank.Common/Services/Ekaer/IShippingToEkaerMapper.cs
index a3430daf..31b2b70b 100644
--- a/FruitBank.Common/Services/Ekaer/IShippingToEkaerMapper.cs
+++ b/FruitBank.Common/Services/Ekaer/IShippingToEkaerMapper.cs
@@ -22,4 +22,11 @@ public interface IShippingToEkaerMapper
/// A bejelentő saját cégadatai (címzett bejövő relációban) + a lerakodási hely.
/// A tradeCard művelet típusa. Alapértelmezés: .
IReadOnlyList MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create);
+
+ ///
+ /// EGY szállítólevelet () képez le egy tradeCard-dá — a dokumentum-szintű
+ /// EKÁER-granularitás egysége (1 dokumentum = 1 tradeCard = 1 TCN). A jármű/fuvarozó adatok a
+ /// document.Shipping-ből jönnek; ha az nincs betöltve, ezek üresen maradnak (a validátor jelzi).
+ ///
+ TradeCardType MapDocument(ShippingDocument document, EkaerCompanyInfo company);
}
diff --git a/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs b/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs
index c038c745..7b221ff3 100644
--- a/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs
+++ b/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs
@@ -42,7 +42,15 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
return operations;
}
- private static TradeCardType BuildTradeCard(Shipping shipping, ShippingDocument document, EkaerCompanyInfo company)
+ public TradeCardType MapDocument(ShippingDocument document, EkaerCompanyInfo company)
+ {
+ ArgumentNullException.ThrowIfNull(document);
+ ArgumentNullException.ThrowIfNull(company);
+
+ return BuildTradeCard(document.Shipping, document, company);
+ }
+
+ private static TradeCardType BuildTradeCard(Shipping? shipping, ShippingDocument document, EkaerCompanyInfo company)
{
var seller = document.Partner; // a beszállító (feladó) — ICompanyInfoBase
@@ -64,7 +72,7 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
DestinationAddress = Truncate(company.FullAddress, 200),
// Fuvarozó (Shipping.CargoPartner). Regisztrált EKAER-azonosító nincs, csak szöveges név.
- CarrierText = shipping.CargoPartner?.Name,
+ CarrierText = shipping?.CargoPartner?.Name,
// Lerakodás = saját telephely (a cégadatból); felrakodás = a beszállító telephelye.
UnloadLocation = company.UnloadLocation,
@@ -72,8 +80,8 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
};
// Vonó jármű + vontatmány: az EKÁER külön bejegyzésként kéri (vehicle / vehicle2).
- if (shipping.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck);
- if (shipping.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer);
+ if (shipping?.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck);
+ if (shipping?.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer);
foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item));
return tradeCard;
diff --git a/FruitBank.Common/SignalRs/SignalRTags.cs b/FruitBank.Common/SignalRs/SignalRTags.cs
index 3c23bee4..77e72728 100644
--- a/FruitBank.Common/SignalRs/SignalRTags.cs
+++ b/FruitBank.Common/SignalRs/SignalRTags.cs
@@ -123,6 +123,8 @@ public class SignalRTags : AcSignalRTags
public const int GetEkaerHistoriesByForeignKey = 187;
public const int AddEkaerHistory = 188;
public const int UpdateEkaerHistory = 189;
+ public const int GenerateEkaerXmlDocument = 190;
+ public const int CreateEkaerHistory = 191;
public const int AuthenticateUser = 195;
public const int RefreshToken = 200;
diff --git a/FruitBankHybrid.Shared.Tests/FruitBankEkaerTests.cs b/FruitBankHybrid.Shared.Tests/FruitBankEkaerTests.cs
new file mode 100644
index 00000000..12bbe5e9
--- /dev/null
+++ b/FruitBankHybrid.Shared.Tests/FruitBankEkaerTests.cs
@@ -0,0 +1,123 @@
+using AyCode.Services.Nav;
+using AyCode.Services.Nav.Ekaer.Models;
+using FruitBank.Common;
+using FruitBank.Common.Entities;
+using FruitBankHybrid.Shared.Services.SignalRs;
+
+namespace FruitBankHybrid.Shared.Tests
+{
+ [TestClass]
+ public sealed class FruitBankEkaerTests
+ {
+ private FruitBankSignalRClient _signalRClient = null!;
+
+ [TestInitialize]
+ public void TestInit()
+ {
+ if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
+
+ _signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankEkaerTests));
+ }
+
+ #region EkaerHistory / Create
+
+ ///
+ /// Backfill: minden meglévő szállítólevélre létrehozza az EkaerHistory rekordot (Pending, XML nélkül).
+ /// A generálás a grid Generate gombjával, kézzel történik. Idempotens: újrafuttatva nem duplikál.
+ ///
+ [TestMethod]
+ public async Task CreateEkaerHistoryForAllShippingDocumentsTest()
+ {
+ var shippingDocuments = await _signalRClient.GetShippingDocuments();
+
+ Assert.IsNotNull(shippingDocuments);
+ Assert.IsNotEmpty(shippingDocuments);
+
+ foreach (var shippingDocument in shippingDocuments)
+ {
+ var ekaerHistory = await _signalRClient.CreateEkaerHistory(shippingDocument.Id, false);
+
+ Console.WriteLine($"doc#{shippingDocument.Id}: EkaerHistory Id: {(ekaerHistory == null ? "NULL VÁLASZ!" : ekaerHistory.Id.ToString())}; Status: {ekaerHistory?.Status}");
+
+ Assert.IsNotNull(ekaerHistory, $"shippingDocument.Id: {shippingDocument.Id}");
+ Assert.IsGreaterThan(0, ekaerHistory.Id, $"shippingDocument.Id: {shippingDocument.Id}");
+ Assert.AreEqual(shippingDocument.Id, ekaerHistory.ForeignKey);
+ Assert.IsFalse(ekaerHistory.IsOutgoing);
+ }
+
+ // Idempotencia: a második hívás a meglévőt adja vissza, nem duplikál.
+ var firstDocumentId = shippingDocuments[0].Id;
+ var again = await _signalRClient.CreateEkaerHistory(firstDocumentId, false);
+ var histories = await _signalRClient.GetEkaerHistoriesByForeignKey(firstDocumentId);
+
+ Assert.IsNotNull(again);
+ Assert.IsNotNull(histories);
+ Assert.AreEqual(1, histories.Count(h => !h.IsOutgoing), $"Duplikált bejövő EkaerHistory; shippingDocumentId: {firstDocumentId}");
+ }
+
+ #endregion EkaerHistory / Create
+
+ #region EkaerHistory / Generate
+
+ ///
+ /// Backfill + teljes Generate-út teszt: minden meglévő szállítólevélre legenerálja az EKÁER XML-t
+ /// (rekord upsert a szerveren), így a grid valós adatot kap és a Generate gomb útvonala tesztelt.
+ ///
+ //[TestMethod] //Kikommentezve: a generálás a grid Generate gombjával, kézzel történik — a teszt később még kelleni fog.
+ public async Task GenerateEkaerXmlDocumentForAllShippingDocumentsTest()
+ {
+ var shippingDocuments = await _signalRClient.GetShippingDocuments();
+
+ Assert.IsNotNull(shippingDocuments);
+ Assert.IsNotEmpty(shippingDocuments);
+
+ foreach (var shippingDocument in shippingDocuments)
+ {
+ var ekaerHistory = await _signalRClient.GenerateEkaerXmlDocument(shippingDocument.Id);
+
+ // A szerver által visszaadott állapot/hibalista logolása — az assertek ELŐTT, hogy hibánál is látsszon.
+ Console.WriteLine($"doc#{shippingDocument.Id}: Status: {(ekaerHistory == null ? "NULL VÁLASZ!" : ekaerHistory.Status.ToString())}");
+ if (!string.IsNullOrWhiteSpace(ekaerHistory?.ErrorText)) Console.WriteLine($" ErrorText: {ekaerHistory.ErrorText}");
+
+ Assert.IsNotNull(ekaerHistory, $"shippingDocument.Id: {shippingDocument.Id}");
+ Assert.AreEqual(shippingDocument.Id, ekaerHistory.ForeignKey);
+ Assert.IsFalse(ekaerHistory.IsOutgoing);
+ Assert.IsFalse(string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc), $"XmlDoc üres; shippingDocument.Id: {shippingDocument.Id}");
+ Assert.IsTrue(ekaerHistory.Status is EkaerStatus.Generated or EkaerStatus.ValidationError,
+ $"Status: {ekaerHistory.Status}; shippingDocument.Id: {shippingDocument.Id}; ErrorText: {ekaerHistory.ErrorText}");
+
+ // A grid útvonala: az XmlDoc visszaolvasható tradeCard-dá.
+ var tradeCard = NavXmlHelper.Deserialize(ekaerHistory.XmlDoc!);
+
+ Assert.IsNotNull(tradeCard);
+ Console.WriteLine($" items: {tradeCard.Items.Count}");
+ }
+ }
+
+ /// Idempotencia: az újragenerálás NEM duplikál — dokumentumonként egy bejövő rekord marad.
+ //[TestMethod] //Kikommentezve: a generálás a grid Generate gombjával, kézzel történik — a teszt később még kelleni fog.
+ public async Task GenerateEkaerXmlDocumentIsIdempotentTest()
+ {
+ var shippingDocuments = await _signalRClient.GetShippingDocuments();
+
+ Assert.IsNotNull(shippingDocuments);
+ Assert.IsNotEmpty(shippingDocuments);
+
+ var shippingDocumentId = shippingDocuments[0].Id;
+
+ var first = await _signalRClient.GenerateEkaerXmlDocument(shippingDocumentId);
+ var second = await _signalRClient.GenerateEkaerXmlDocument(shippingDocumentId);
+
+ Assert.IsNotNull(first);
+ Assert.IsNotNull(second);
+ Assert.AreEqual(first.Id, second.Id, "Az újragenerálás új rekordot hozott létre frissítés helyett!");
+
+ var histories = await _signalRClient.GetEkaerHistoriesByForeignKey(shippingDocumentId);
+
+ Assert.IsNotNull(histories);
+ Assert.AreEqual(1, histories.Count(h => !h.IsOutgoing), $"Duplikált bejövő EkaerHistory; shippingDocumentId: {shippingDocumentId}");
+ }
+
+ #endregion EkaerHistory / Generate
+ }
+}
diff --git a/FruitBankHybrid.Shared/Components/Grids/Cargos/GridCargoPartner.razor b/FruitBankHybrid.Shared/Components/Grids/Cargos/GridCargoPartner.razor
index 07ea7f23..626bcf65 100644
--- a/FruitBankHybrid.Shared/Components/Grids/Cargos/GridCargoPartner.razor
+++ b/FruitBankHybrid.Shared/Components/Grids/Cargos/GridCargoPartner.razor
@@ -38,8 +38,8 @@
-
-
+
+
diff --git a/FruitBankHybrid.Shared/Components/Grids/Cargos/GridCargoTruck.razor b/FruitBankHybrid.Shared/Components/Grids/Cargos/GridCargoTruck.razor
index 3663e202..61144166 100644
--- a/FruitBankHybrid.Shared/Components/Grids/Cargos/GridCargoTruck.razor
+++ b/FruitBankHybrid.Shared/Components/Grids/Cargos/GridCargoTruck.razor
@@ -32,8 +32,8 @@
-
-
+
+
diff --git a/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistory.razor b/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistory.razor
index ed881786..be85e3d2 100644
--- a/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistory.razor
+++ b/FruitBankHybrid.Shared/Components/Grids/Ekaers/GridEkaerHistory.razor
@@ -29,8 +29,8 @@
-
-
+
+
diff --git a/FruitBankHybrid.Shared/Components/Grids/Partners/GridPartner.razor b/FruitBankHybrid.Shared/Components/Grids/Partners/GridPartner.razor
index 61947300..c4e9a301 100644
--- a/FruitBankHybrid.Shared/Components/Grids/Partners/GridPartner.razor
+++ b/FruitBankHybrid.Shared/Components/Grids/Partners/GridPartner.razor
@@ -38,8 +38,8 @@
-
-
+
+
diff --git a/FruitBankHybrid.Shared/Components/Grids/Partners/GridPartnerDepot.razor b/FruitBankHybrid.Shared/Components/Grids/Partners/GridPartnerDepot.razor
index 26a090f1..0862fd24 100644
--- a/FruitBankHybrid.Shared/Components/Grids/Partners/GridPartnerDepot.razor
+++ b/FruitBankHybrid.Shared/Components/Grids/Partners/GridPartnerDepot.razor
@@ -35,8 +35,8 @@
-
-
+
+
@*
diff --git a/FruitBankHybrid.Shared/Components/Grids/Shippings/GridShipping.razor b/FruitBankHybrid.Shared/Components/Grids/Shippings/GridShipping.razor
index c668cd3a..5a9a2378 100644
--- a/FruitBankHybrid.Shared/Components/Grids/Shippings/GridShipping.razor
+++ b/FruitBankHybrid.Shared/Components/Grids/Shippings/GridShipping.razor
@@ -86,8 +86,8 @@
-
-
+
+
diff --git a/FruitBankHybrid.Shared/Services/SignalRs/FruitBankSignalRClient.cs b/FruitBankHybrid.Shared/Services/SignalRs/FruitBankSignalRClient.cs
index 1f10aa12..8dbe9808 100644
--- a/FruitBankHybrid.Shared/Services/SignalRs/FruitBankSignalRClient.cs
+++ b/FruitBankHybrid.Shared/Services/SignalRs/FruitBankSignalRClient.cs
@@ -79,6 +79,8 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
public Task?> GetEkaerHistoriesByForeignKey(int foreignKey) => GetAllAsync>(SignalRTags.GetEkaerHistoriesByForeignKey, [foreignKey]);
public Task AddEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.AddEkaerHistory, ekaerHistory);
public Task UpdateEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.UpdateEkaerHistory, ekaerHistory);
+ public Task GenerateEkaerXmlDocument(int shippingDocumentId) => GetByIdAsync(SignalRTags.GenerateEkaerXmlDocument, shippingDocumentId);
+ public Task CreateEkaerHistory(int foreignKey, bool isOutgoing) => GetByIdAsync(SignalRTags.CreateEkaerHistory, [foreignKey, isOutgoing]);
#endregion EkaerHistory
#region CargoPartner