175 lines
9.7 KiB
C#
175 lines
9.7 KiB
C#
using AyCode.Services.Nav;
|
|
using AyCode.Services.Nav.Ekaer;
|
|
using AyCode.Services.Nav.Ekaer.Models;
|
|
using FruitBank.Common.Dtos;
|
|
using FruitBank.Common.Entities;
|
|
using FruitBank.Common.Services.Ekaer;
|
|
|
|
namespace FruitBank.Common.Server.Services.Ekaer;
|
|
|
|
/// <inheritdoc cref="IFruitBankEkaerService"/>
|
|
/// <remarks>
|
|
/// A teljes lánc: <c>map</c> (<see cref="IShippingToEkaerMapper"/>, FruitBank.Common) →
|
|
/// <c>validate → send</c> (<see cref="IEkaerSubmitService"/>, AyCode.Services). A saját cégadatot
|
|
/// (<see cref="EkaerSettings.Company"/>) és a NAV-fiók hitelesítő adatait a DI szolgáltatja.
|
|
/// </remarks>
|
|
public sealed class FruitBankEkaerService : IFruitBankEkaerService
|
|
{
|
|
private readonly IShippingToEkaerMapper _mapper;
|
|
private readonly IEkaerSubmitService _submitService;
|
|
private readonly IEkaerTradeCardValidator _validator;
|
|
private readonly IEkaerSettings _settings;
|
|
|
|
public FruitBankEkaerService(IShippingToEkaerMapper mapper, IEkaerSubmitService submitService, IEkaerTradeCardValidator validator, IEkaerSettings settings)
|
|
{
|
|
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
|
_submitService = submitService ?? throw new ArgumentNullException(nameof(submitService));
|
|
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
|
|
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
|
}
|
|
|
|
public Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(shipping);
|
|
|
|
// map (FruitBank.Common) → submit: validate → send (AyCode.Services)
|
|
var operations = _mapper.MapShipping(shipping, _settings.Company, operation);
|
|
return _submitService.SubmitAsync(operations, cancellationToken);
|
|
}
|
|
|
|
public EkaerHistory GenerateEkaerXmlDocument(IReadOnlyCollection<ShippingDocument> documents, EkaerHistory? ekaerHistory = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(documents);
|
|
if (documents.Count == 0) throw new ArgumentException("documents is empty", nameof(documents));
|
|
ekaerHistory ??= new EkaerHistory { IsOutgoing = false };
|
|
|
|
// A csoport azonos Partneré (a kapu így csoportosít) → az első dokumentum pénzneme a mérvadó.
|
|
var currency = documents.First().Partner?.Currency;
|
|
// Guardok a leképezés ELŐTT (nincs félrevezető XML), majd a csoport ÖSSZES dokumentuma EGY tradeCard-dá
|
|
// (összevont tömeg/érték): ToConsignment → BuildTradeCard.
|
|
return TryDepotError(ekaerHistory, documents)
|
|
?? TryConfigError(ekaerHistory, currency)
|
|
?? Finalize(ekaerHistory, _mapper.BuildTradeCard(_mapper.ToConsignment(documents, _settings.Company)), currency);
|
|
}
|
|
|
|
public EkaerHistory GenerateEkaerXmlDocument(OrderDto order, EkaerHistory? ekaerHistory = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(order);
|
|
ekaerHistory ??= new EkaerHistory { IsOutgoing = true };
|
|
|
|
// Kimenő pénznem: jelenleg minden HUF (a deviza az OrderDto-ba kerül, amint bekötik) → ConversionRate = 1.
|
|
const string currency = "HUF";
|
|
return TryConfigError(ekaerHistory, currency) ?? Finalize(ekaerHistory, _mapper.MapOrder(order, _settings.Company), currency);
|
|
}
|
|
|
|
public EkaerObligationResult EvaluateObligation(IReadOnlyCollection<ShippingDocument> documents)
|
|
=> EkaerReportability.Evaluate(_mapper.ToConsignment(documents, _settings.Company), _settings);
|
|
|
|
public EkaerObligationResult EvaluateObligation(OrderDto order)
|
|
=> EkaerReportability.Evaluate(_mapper.ToConsignment(order, _settings.Company), _settings);
|
|
|
|
public void SetSummary(EkaerHistory history, DateTime? shippingDate, string? partner)
|
|
{
|
|
history.ShippingDate = shippingDate;
|
|
history.Partner = partner;
|
|
}
|
|
|
|
public async Task<EkaerHistory> SendEkaerAsync(EkaerHistory ekaerHistory, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(ekaerHistory);
|
|
|
|
// Csak legenerált, blokkoló hiba nélküli (warninggal is) sor küldhető — a kliens is így gátol, de itt is védünk.
|
|
if (!ekaerHistory.Status.IsSubmittable() || string.IsNullOrWhiteSpace(ekaerHistory.XmlDoc))
|
|
{
|
|
ekaerHistory.Status = EkaerStatus.SendError;
|
|
ekaerHistory.ErrorText = "[Error] A bejelentés nem küldhető be: nincs legenerált, érvényes XML — előbb Generate.";
|
|
return ekaerHistory;
|
|
}
|
|
|
|
// A warning-eredetet a küldés ELŐTT rögzítjük (siker esetén ez dönt Sent vs SentWithMissingData között).
|
|
var hadWarning = ekaerHistory.Status == EkaerStatus.GeneratedWithWarning;
|
|
|
|
try
|
|
{
|
|
// A TÁROLT tradeCard-ot küldjük (amit a Generate előállított + validált), create-műveletként.
|
|
var tradeCard = NavXmlHelper.Deserialize<TradeCardType>(ekaerHistory.XmlDoc);
|
|
var operation = new TradeCardOperationType { Index = 1, Operation = OperationType.Create, TradeCard = tradeCard };
|
|
|
|
var result = await _submitService.SubmitAsync([operation], cancellationToken).ConfigureAwait(false);
|
|
|
|
// Elvileg nem fordul elő (a submittable sort a Generate már validálta), de defenzíven kezeljük.
|
|
if (!result.IsValid)
|
|
{
|
|
ekaerHistory.Status = EkaerStatus.ValidationError;
|
|
ekaerHistory.ErrorText = string.Join(Environment.NewLine, result.ValidationErrors.Select(e => $"[Error] {e.ErrorMessage}"));
|
|
return ekaerHistory;
|
|
}
|
|
|
|
// Siker: a NAV visszaadta az EKÁER-számot (tcn). Warningos generálásból SentWithMissingData (pótlásra vár), egyébként Sent.
|
|
ekaerHistory.EkaerNumber = result.Response?.TradeCardOperationsResults.FirstOrDefault()?.TradeCardInfo?.Tcn;
|
|
ekaerHistory.SentDate = DateTime.UtcNow;
|
|
ekaerHistory.Status = hadWarning ? EkaerStatus.SentWithMissingData : EkaerStatus.Sent;
|
|
// Tiszta beküldés → nincs üzenet; warningnál a meglévő szöveg (pl. hiányzó rendszám) MARAD (a pótlandó adat jelzése).
|
|
if (!hadWarning) ekaerHistory.ErrorText = null;
|
|
return ekaerHistory;
|
|
}
|
|
catch (NavReportException ex)
|
|
{
|
|
ekaerHistory.Status = EkaerStatus.SendError;
|
|
ekaerHistory.ErrorText = $"[Error] NAV ({ex.ReasonCode ?? ex.HttpStatusCode?.ToString() ?? "hiba"}): {ex.Message}";
|
|
return ekaerHistory;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ekaerHistory.Status = EkaerStatus.SendError;
|
|
ekaerHistory.ErrorText = $"[Error] Beküldési hiba: {ex.Message}";
|
|
return ekaerHistory;
|
|
}
|
|
}
|
|
|
|
/// <summary>Depot-kapu (T-D8R4): a PartnerDepot az EKÁER-hez kötelező — a felrakodási hely a depot címe. Hiányzó
|
|
/// vagy be nem töltött telephelynél a leképezés ELŐTT ValidationError (nincs félrevezető, partner-központi-címes XML).
|
|
/// <c>null</c> = rendben, mehet a generálás.</summary>
|
|
private static EkaerHistory? TryDepotError(EkaerHistory ekaerHistory, IReadOnlyCollection<ShippingDocument> documents)
|
|
{
|
|
if (documents.All(d => d.PartnerDepot != null)) return null;
|
|
|
|
ekaerHistory.Status = EkaerStatus.ValidationError;
|
|
ekaerHistory.ErrorText = "A szállítólevélhez nincs telephely (PartnerDepot) rendelve — EKÁER-hez kötelező (a felrakodási hely a telephely címe); pótlás után generáld újra.";
|
|
return ekaerHistory;
|
|
}
|
|
|
|
/// <summary>Config-kapu: külföldi (nem HUF) feladónál az árfolyam kötelező — különben a leképezés ELŐTT
|
|
/// ValidationError (nincs félrevezető XML). <c>null</c> = rendben, mehet a generálás.</summary>
|
|
private EkaerHistory? TryConfigError(EkaerHistory ekaerHistory, string? currency)
|
|
{
|
|
if (EkaerValueCalculator.IsHuf(currency) || _settings.EurHufRate > 0) return null;
|
|
|
|
ekaerHistory.Status = EkaerStatus.ValidationError;
|
|
ekaerHistory.ErrorText = "EKÁER EUR-HUF árfolyam nincs konfigurálva (appsettings Ekaer:ExchangeRate:EurHuf) — a tétel-érték nem számolható.";
|
|
return ekaerHistory;
|
|
}
|
|
|
|
/// <summary>Közös befejezés (bejövő/kimenő): validál, szerializál; az XML validációs hibánál IS tárolódik
|
|
/// (a detail-nézethez), és rögzíti a ténylegesen alkalmazott árfolyamot (HUF → 1, külföldi → FX-ráta).</summary>
|
|
private EkaerHistory Finalize(EkaerHistory ekaerHistory, TradeCardType tradeCard, string? currency)
|
|
{
|
|
var operation = new TradeCardOperationType { Index = 1, Operation = OperationType.Create, TradeCard = tradeCard };
|
|
var messages = _validator.Validate(operation);
|
|
|
|
ekaerHistory.XmlDoc = NavXmlHelper.Serialize(tradeCard);
|
|
ekaerHistory.ConversionRate = EkaerValueCalculator.ResolveRateToHuf(currency, _settings.EurHufRate);
|
|
// Blokkoló hiba → ValidationError (nem küldhető); csak warning → GeneratedWithWarning (küldhető, de pótlandó);
|
|
// semmi → Generated. Az üzeneteket súlyossággal prefixeljük, hogy a detail-nézet megkülönböztesse őket.
|
|
ekaerHistory.Status = messages.HasErrors() ? EkaerStatus.ValidationError
|
|
: messages.HasWarnings() ? EkaerStatus.GeneratedWithWarning
|
|
: EkaerStatus.Generated;
|
|
// Error-ok elöl, warningok hátul (a detail-nézet soronként színez); a prefix [Error]/[Warning] hordozza a szintet.
|
|
ekaerHistory.ErrorText = messages.Count == 0 ? null
|
|
: string.Join(Environment.NewLine, messages
|
|
.OrderByDescending(m => m.GetSeverity())
|
|
.Select(m => $"[{m.GetSeverity()}] {m.ErrorMessage}"));
|
|
return ekaerHistory;
|
|
}
|
|
}
|