Add EKÁER XML generation, validation, and tests

- Added GenerateEkaerXmlDocument to service and interface for EKÁER tradeCard XML generation and validation from ShippingDocument.
- Extended IShippingToEkaerMapper to support document-level mapping.
- Updated controller and SignalR client/interfaces with new methods and tags for EKÁER XML and EkaerHistory creation.
- Formatted Created/Modified columns in Blazor grids.
- Added tests for EkaerHistory creation, XML generation, and idempotency.
- Improved null-safety and argument validation in mapping logic.
This commit is contained in:
Loretta 2026-06-10 18:09:33 +02:00
parent 82f0f00196
commit 76cb8adbe6
14 changed files with 197 additions and 17 deletions

View File

@ -1,3 +1,4 @@
using AyCode.Services.Nav;
using AyCode.Services.Nav.Ekaer; using AyCode.Services.Nav.Ekaer;
using AyCode.Services.Nav.Ekaer.Models; using AyCode.Services.Nav.Ekaer.Models;
using FruitBank.Common.Entities; using FruitBank.Common.Entities;
@ -15,12 +16,14 @@ public sealed class FruitBankEkaerService : IFruitBankEkaerService
{ {
private readonly IShippingToEkaerMapper _mapper; private readonly IShippingToEkaerMapper _mapper;
private readonly IEkaerSubmitService _submitService; private readonly IEkaerSubmitService _submitService;
private readonly IEkaerTradeCardValidator _validator;
private readonly EkaerCompanyInfo _company; 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)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_submitService = submitService ?? throw new ArgumentNullException(nameof(submitService)); _submitService = submitService ?? throw new ArgumentNullException(nameof(submitService));
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
_company = company ?? throw new ArgumentNullException(nameof(company)); _company = company ?? throw new ArgumentNullException(nameof(company));
} }
@ -32,4 +35,27 @@ public sealed class FruitBankEkaerService : IFruitBankEkaerService
var operations = _mapper.MapShipping(shipping, _company, operation); var operations = _mapper.MapShipping(shipping, _company, operation);
return _submitService.SubmitAsync(operations, cancellationToken); 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;
}
} }

View File

@ -16,4 +16,14 @@ public interface IFruitBankEkaerService
/// vagy a NAV-válasz — lásd <see cref="EkaerSubmitResult"/>. /// vagy a NAV-válasz — lásd <see cref="EkaerSubmitResult"/>.
/// </summary> /// </summary>
Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default); Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default);
/// <summary>
/// Egy szállítólevélből legenerálja az EKÁER tradeCard XML-t és validálja — a (meglévő vagy új)
/// <see cref="EkaerHistory"/> rekordot tölti: <c>XmlDoc</c> + <c>Status</c>
/// (<see cref="EkaerStatus.Generated"/> / <see cref="EkaerStatus.ValidationError"/>) + <c>ErrorText</c>.
/// NEM perzisztál és NEM hív NAV-ot — a mentés (upsert) a hívó SignalR endpoint dolga.
/// </summary>
/// <param name="document">A szállítólevél a betöltött gráffal (Partner, Items+ProductDto, Shipping→járművek).</param>
/// <param name="ekaerHistory">A dokumentum meglévő rekordja (újrageneráláskor); <c>null</c> → új rekord készül.</param>
EkaerHistory GenerateEkaerXmlDocument(ShippingDocument document, EkaerHistory? ekaerHistory = null);
} }

View File

@ -34,6 +34,8 @@ public interface IFruitBankDataControllerCommon
public Task<List<EkaerHistory>?> GetEkaerHistoriesByForeignKey(int foreignKey); public Task<List<EkaerHistory>?> GetEkaerHistoriesByForeignKey(int foreignKey);
public Task<EkaerHistory?> AddEkaerHistory(EkaerHistory ekaerHistory); public Task<EkaerHistory?> AddEkaerHistory(EkaerHistory ekaerHistory);
public Task<EkaerHistory?> UpdateEkaerHistory(EkaerHistory ekaerHistory); public Task<EkaerHistory?> UpdateEkaerHistory(EkaerHistory ekaerHistory);
public Task<EkaerHistory?> GenerateEkaerXmlDocument(int shippingDocumentId);
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing);
#endregion EkaerHistory #endregion EkaerHistory
#region CargoPartner #region CargoPartner

View File

@ -22,4 +22,11 @@ public interface IShippingToEkaerMapper
/// <param name="company">A bejelentő saját cégadatai (címzett bejövő relációban) + a lerakodási hely.</param> /// <param name="company">A bejelentő saját cégadatai (címzett bejövő relációban) + a lerakodási hely.</param>
/// <param name="operation">A tradeCard művelet típusa. Alapértelmezés: <see cref="OperationType.Create"/>.</param> /// <param name="operation">A tradeCard művelet típusa. Alapértelmezés: <see cref="OperationType.Create"/>.</param>
IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create); IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create);
/// <summary>
/// EGY szállítólevelet (<see cref="ShippingDocument"/>) 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
/// <c>document.Shipping</c>-ből jönnek; ha az nincs betöltve, ezek üresen maradnak (a validátor jelzi).
/// </summary>
TradeCardType MapDocument(ShippingDocument document, EkaerCompanyInfo company);
} }

View File

@ -42,7 +42,15 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
return operations; 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 var seller = document.Partner; // a beszállító (feladó) — ICompanyInfoBase
@ -64,7 +72,7 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
DestinationAddress = Truncate(company.FullAddress, 200), DestinationAddress = Truncate(company.FullAddress, 200),
// Fuvarozó (Shipping.CargoPartner). Regisztrált EKAER-azonosító nincs, csak szöveges név. // 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. // Lerakodás = saját telephely (a cégadatból); felrakodás = a beszállító telephelye.
UnloadLocation = company.UnloadLocation, 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). // 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?.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck);
if (shipping.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer); if (shipping?.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer);
foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item)); foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item));
return tradeCard; return tradeCard;

View File

@ -123,6 +123,8 @@ public class SignalRTags : AcSignalRTags
public const int GetEkaerHistoriesByForeignKey = 187; public const int GetEkaerHistoriesByForeignKey = 187;
public const int AddEkaerHistory = 188; public const int AddEkaerHistory = 188;
public const int UpdateEkaerHistory = 189; public const int UpdateEkaerHistory = 189;
public const int GenerateEkaerXmlDocument = 190;
public const int CreateEkaerHistory = 191;
public const int AuthenticateUser = 195; public const int AuthenticateUser = 195;
public const int RefreshToken = 200; public const int RefreshToken = 200;

View File

@ -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
/// <summary>
/// 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.
/// </summary>
[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
/// <summary>
/// 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.
/// </summary>
//[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<TradeCardType>(ekaerHistory.XmlDoc!);
Assert.IsNotNull(tradeCard);
Console.WriteLine($" items: {tradeCard.Items.Count}");
}
}
/// <summary>Idempotencia: az újragenerálás NEM duplikál — dokumentumonként egy bejövő rekord marad.</summary>
//[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
}
}

View File

@ -38,8 +38,8 @@
<DxGridDataColumn FieldName="City" /> <DxGridDataColumn FieldName="City" />
<DxGridDataColumn FieldName="Street" /> <DxGridDataColumn FieldName="Street" />
<DxGridDataColumn FieldName="Created" ReadOnly="true" /> <DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridDataColumn FieldName="Modified" ReadOnly="true" /> <DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn> <DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
</Columns> </Columns>
<DetailRowTemplate> <DetailRowTemplate>

View File

@ -32,8 +32,8 @@
<DxGridDataColumn FieldName="@nameof(CargoTruck.CountryCode)" /> <DxGridDataColumn FieldName="@nameof(CargoTruck.CountryCode)" />
<DxGridDataColumn FieldName="@nameof(CargoTruck.IsTrailer)" /> <DxGridDataColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
<DxGridDataColumn FieldName="Created" ReadOnly="true" /> <DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridDataColumn FieldName="Modified" ReadOnly="true" /> <DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn> <DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
</Columns> </Columns>
<ToolbarTemplate> <ToolbarTemplate>

View File

@ -29,8 +29,8 @@
<DxGridDataColumn FieldName="@nameof(EkaerHistory.ForeignKey)" /> <DxGridDataColumn FieldName="@nameof(EkaerHistory.ForeignKey)" />
<DxGridDataColumn FieldName="@nameof(EkaerHistory.IsOutgoing)" /> <DxGridDataColumn FieldName="@nameof(EkaerHistory.IsOutgoing)" />
<DxGridDataColumn FieldName="Created" ReadOnly="true" /> <DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridDataColumn FieldName="Modified" ReadOnly="true" /> <DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn> <DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
</Columns> </Columns>
<DetailRowTemplate> <DetailRowTemplate>

View File

@ -38,8 +38,8 @@
<DxGridDataColumn FieldName="City" /> <DxGridDataColumn FieldName="City" />
<DxGridDataColumn FieldName="Street" /> <DxGridDataColumn FieldName="Street" />
<DxGridDataColumn FieldName="Created" ReadOnly="true" /> <DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridDataColumn FieldName="Modified" ReadOnly="true" /> <DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn> <DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
</Columns> </Columns>
<DetailRowTemplate> <DetailRowTemplate>

View File

@ -35,8 +35,8 @@
<DxGridDataColumn FieldName="City" /> <DxGridDataColumn FieldName="City" />
<DxGridDataColumn FieldName="Street" /> <DxGridDataColumn FieldName="Street" />
<DxGridDataColumn FieldName="Created" ReadOnly="true" /> <DxGridDataColumn FieldName="Created" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridDataColumn FieldName="Modified" ReadOnly="true" /> <DxGridDataColumn FieldName="Modified" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn> <DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
</Columns> </Columns>
@* <DetailRowTemplate> @* <DetailRowTemplate>

View File

@ -86,8 +86,8 @@
<DxGridDataColumn FieldName="Comment" Caption="Megjegyzés" ReadOnly="false" /> <DxGridDataColumn FieldName="Comment" Caption="Megjegyzés" ReadOnly="false" />
<DxGridDataColumn FieldName="CargoCompany" Caption="Fuvarozó" ReadOnly="false" /> <DxGridDataColumn FieldName="CargoCompany" Caption="Fuvarozó" ReadOnly="false" />
<DxGridDataColumn FieldName="IsAllMeasured" Caption="Mérések kész" ReadOnly="true" /> <DxGridDataColumn FieldName="IsAllMeasured" Caption="Mérések kész" ReadOnly="true" />
<DxGridDataColumn FieldName="Created" Caption="Létrehozva" ReadOnly="true" /> <DxGridDataColumn FieldName="Created" Caption="Létrehozva" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridDataColumn FieldName="Modified" Caption="Módosítva" ReadOnly="true" /> <DxGridDataColumn FieldName="Modified" Caption="Módosítva" ReadOnly="true" DisplayFormat="yyyy.MM.dd hh:mm" />
<DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn> <DxGridCommandColumn Visible="!IsMasterGrid" Width="120"></DxGridCommandColumn>
</Columns> </Columns>
<DetailRowTemplate> <DetailRowTemplate>

View File

@ -79,6 +79,8 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
public Task<List<EkaerHistory>?> GetEkaerHistoriesByForeignKey(int foreignKey) => GetAllAsync<List<EkaerHistory>>(SignalRTags.GetEkaerHistoriesByForeignKey, [foreignKey]); public Task<List<EkaerHistory>?> GetEkaerHistoriesByForeignKey(int foreignKey) => GetAllAsync<List<EkaerHistory>>(SignalRTags.GetEkaerHistoriesByForeignKey, [foreignKey]);
public Task<EkaerHistory?> AddEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.AddEkaerHistory, ekaerHistory); public Task<EkaerHistory?> AddEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.AddEkaerHistory, ekaerHistory);
public Task<EkaerHistory?> UpdateEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.UpdateEkaerHistory, ekaerHistory); public Task<EkaerHistory?> UpdateEkaerHistory(EkaerHistory ekaerHistory) => PostDataAsync(SignalRTags.UpdateEkaerHistory, ekaerHistory);
public Task<EkaerHistory?> GenerateEkaerXmlDocument(int shippingDocumentId) => GetByIdAsync<EkaerHistory?>(SignalRTags.GenerateEkaerXmlDocument, shippingDocumentId);
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing) => GetByIdAsync<EkaerHistory?>(SignalRTags.CreateEkaerHistory, [foreignKey, isOutgoing]);
#endregion EkaerHistory #endregion EkaerHistory
#region CargoPartner #region CargoPartner