EKÁER: unify consignment mapping & obligation logic
- Introduced EkaerConsignment model for direction-agnostic mapping (inbound/outbound), centralizing normalization and aggregation. - Refactored IShippingToEkaerMapper and service interfaces to use new model and expose obligation evaluation. - Added EkaerReportability logic for robust, threshold-based reporting obligation checks with error handling. - Updated UI and SignalR to support detailed creation results and user feedback for skipped entries. - Enhanced tests and documentation to cover new mapping, evaluation, and legal context. - Minor config/protocol adjustments for improved reliability.
This commit is contained in:
parent
6f46aaebb0
commit
c722a7b242
|
|
@ -69,7 +69,11 @@
|
||||||
"Bash(grep -c -a \"EurHufRate\" FruitBank.Common.dll FruitBank.Common.Server.dll)",
|
"Bash(grep -c -a \"EurHufRate\" FruitBank.Common.dll FruitBank.Common.Server.dll)",
|
||||||
"Bash(ls -la --time-style=+%H:%M FruitBank.Common.dll FruitBank.Common.Server.dll)",
|
"Bash(ls -la --time-style=+%H:%M FruitBank.Common.dll FruitBank.Common.Server.dll)",
|
||||||
"Bash(find \"H:\\\\\\\\Applications\\\\\\\\Mango\" -name \"Order.cs\" -path \"*/Domain/Orders/*\" 2>&1 | head -3)",
|
"Bash(find \"H:\\\\\\\\Applications\\\\\\\\Mango\" -name \"Order.cs\" -path \"*/Domain/Orders/*\" 2>&1 | head -3)",
|
||||||
"Bash(f=/h/Applications/Aycode/Source/AyCode.Blazor/AyCode.Blazor.Components/Components/Grids/MgGridBase.razor; echo \"FILE exists: $\\(test -f $f && echo yes\\)\"; echo \"=== DxGrid nyitó tag + környéke ===\"; grep -n \"DxGrid\\\\|CustomizeDataRowFilter\\\\|CustomizeElement\\\\|@attributes\\\\|CustomizeEditModel\" \"$f\" 2>/dev/null | head -15)"
|
"Bash(f=/h/Applications/Aycode/Source/AyCode.Blazor/AyCode.Blazor.Components/Components/Grids/MgGridBase.razor; echo \"FILE exists: $\\(test -f $f && echo yes\\)\"; echo \"=== DxGrid nyitó tag + környéke ===\"; grep -n \"DxGrid\\\\|CustomizeDataRowFilter\\\\|CustomizeElement\\\\|@attributes\\\\|CustomizeEditModel\" \"$f\" 2>/dev/null | head -15)",
|
||||||
|
"WebFetch(domain:www.ekaer-feladas.hu)",
|
||||||
|
"WebFetch(domain:net.jogtar.hu)",
|
||||||
|
"WebFetch(domain:www.itrack.hu)",
|
||||||
|
"WebFetch(domain:docplayer.hu)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,12 @@ public sealed class FruitBankEkaerService : IFruitBankEkaerService
|
||||||
return TryConfigError(ekaerHistory, currency) ?? Finalize(ekaerHistory, _mapper.MapOrder(order, _settings.Company), currency);
|
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);
|
||||||
|
|
||||||
/// <summary>Config-kapu: külföldi (nem HUF) feladónál az árfolyam kötelező — különben a leképezés ELŐTT
|
/// <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>
|
/// ValidationError (nincs félrevezető XML). <c>null</c> = rendben, mehet a generálás.</summary>
|
||||||
private EkaerHistory? TryConfigError(EkaerHistory ekaerHistory, string? currency)
|
private EkaerHistory? TryConfigError(EkaerHistory ekaerHistory, string? currency)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using AyCode.Services.Nav.Ekaer;
|
||||||
using AyCode.Services.Nav.Ekaer.Models;
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
using FruitBank.Common.Dtos;
|
using FruitBank.Common.Dtos;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
|
using FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
namespace FruitBank.Common.Server.Services.Ekaer;
|
namespace FruitBank.Common.Server.Services.Ekaer;
|
||||||
|
|
||||||
|
|
@ -34,4 +35,14 @@ public interface IFruitBankEkaerService
|
||||||
/// ELADÓ, a vevő a CÍMZETT (belföldi értékesítés). NEM perzisztál és NEM hív NAV-ot — a mentés a hívó dolga.
|
/// ELADÓ, a vevő a CÍMZETT (belföldi értékesítés). NEM perzisztál és NEM hív NAV-ot — a mentés a hívó dolga.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EkaerHistory GenerateEkaerXmlDocument(OrderDto order, EkaerHistory? ekaerHistory = null);
|
EkaerHistory GenerateEkaerXmlDocument(OrderDto order, EkaerHistory? ekaerHistory = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Eldönti egy bejövő (Shipping, Partner) CSOPORT EKÁER-kötelezettségét: külföldi (a feladó és a címzett
|
||||||
|
/// országkódja eltér) → mindig kötelező; belföld → az AGGREGÁLT tömeg/érték a küszöbhöz; érvénytelen országkód →
|
||||||
|
/// <see cref="EkaerObligation.DataError"/>. A küszöb-summa a csoport ÖSSZES dokumentumának tételeire megy.
|
||||||
|
/// </summary>
|
||||||
|
EkaerObligationResult EvaluateObligation(IReadOnlyCollection<ShippingDocument> documents);
|
||||||
|
|
||||||
|
/// <summary>Eldönti egy kimenő rendelés EKÁER-kötelezettségét (ugyanaz a logika, a rendelés tételeire).</summary>
|
||||||
|
EkaerObligationResult EvaluateObligation(OrderDto order);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using AyCode.Core.Serializers.Attributes;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Dtos;
|
||||||
|
|
||||||
|
/// <summary>A „hiányzó EKÁER-sorok létrehozása" (reconciliation gomb) eredménye a SignalR-dróton: hány Pending sor
|
||||||
|
/// jött létre, és a felhasználónak szóló sima string üzenetek (adathiány / formátumhiba miatt kihagyott jelöltek —
|
||||||
|
/// pl. érvénytelen országkód). A küszöb alatti, nem kötelező jelölteknél NINCS üzenet. A megjelenítés a hívó dolga.</summary>
|
||||||
|
[AcBinarySerializable(false, false, false, false, false, false)]
|
||||||
|
public sealed class EkaerCreateResult
|
||||||
|
{
|
||||||
|
public int CreatedCount { get; set; }
|
||||||
|
public List<string> Messages { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ public interface IFruitBankDataControllerCommon
|
||||||
public Task<EkaerHistory?> UpdateEkaerHistory(EkaerHistory ekaerHistory);
|
public Task<EkaerHistory?> UpdateEkaerHistory(EkaerHistory ekaerHistory);
|
||||||
public Task<EkaerHistory?> GenerateEkaerXmlDocument(int foreignKey, bool isOutgoing);
|
public Task<EkaerHistory?> GenerateEkaerXmlDocument(int foreignKey, bool isOutgoing);
|
||||||
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing);
|
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing);
|
||||||
public Task<int> CreateMissingEkaerHistories(DateTime fromDate);
|
public Task<EkaerCreateResult?> CreateMissingEkaerHistories(DateTime fromDate);
|
||||||
public Task<int> GetEkaerHistoryCount(EkaerHistoryFilter filter);
|
public Task<int> GetEkaerHistoryCount(EkaerHistoryFilter filter);
|
||||||
#endregion EkaerHistory
|
#endregion EkaerHistory
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
using AyCode.Services.Nav.Ekaer.Models;
|
||||||
|
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Irány-független, normalizált szállítmány-modell. A bejövő (<c>ShippingDocument</c>-csoport) és a kimenő
|
||||||
|
/// (<c>OrderDto</c>) forrás EBBE képződik (a <see cref="IShippingToEkaerMapper"/> adapterei), és innen épül MIND a
|
||||||
|
/// NAV tradeCard (<see cref="IShippingToEkaerMapper.BuildTradeCard"/>), MIND a bejelentés-kötelezettség
|
||||||
|
/// (<see cref="EkaerReportability"/>). Így az irányfüggő tudás a két adapterre szorul, a közös logika egy helyen van.
|
||||||
|
/// Szerver-oldali köztes típus — NEM megy a SignalR-dróton.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A fel-/lerakodási hely és a jármű már a NAV-típus (<see cref="LocationType"/>, <see cref="BasicVehicleDetailType"/>),
|
||||||
|
/// mert a saját telephely (<c>company.Site</c>) kész LocationType (Phone/Email/FELIR-mezőkkel) — azt átalakítás nélkül,
|
||||||
|
/// MEZŐVESZTÉS nélkül kell átengedni. A normalizálás (kötelezettséghez számít) a feladó/címzett országkódjára és a
|
||||||
|
/// tételek tömeg/érték-aggregálására korlátozódik.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class EkaerConsignment
|
||||||
|
{
|
||||||
|
/// <summary>A forrás azonosítója (bejövőnél ShippingDocument.Id, kimenőnél Order.Id). Csoport-kiértékelésnél
|
||||||
|
/// (több dokumentum) az első forrásé — a kötelezettség-döntés nem használja.</summary>
|
||||||
|
public int ForeignKey { get; init; }
|
||||||
|
|
||||||
|
public bool IsOutgoing { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Számla-pénznem (ISO 4217). A tétel <see cref="EkaerLine.ValueHuf"/> már KISZÁMOLT HUF-ban.</summary>
|
||||||
|
public string? Currency { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Feladó / eladó (a kötelezettség az országkódját nézi).</summary>
|
||||||
|
public EkaerEndpoint Seller { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>Címzett / vevő (a kötelezettség az országkódját nézi).</summary>
|
||||||
|
public EkaerEndpoint Buyer { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>Felrakodási hely (NAV LocationType — pl. a saját telephely átengedve).</summary>
|
||||||
|
public LocationType? LoadLocation { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Lerakodási hely (NAV LocationType — pl. a saját telephely átengedve).</summary>
|
||||||
|
public LocationType? UnloadLocation { get; init; }
|
||||||
|
|
||||||
|
public IReadOnlyList<EkaerLine> Lines { get; init; } = [];
|
||||||
|
|
||||||
|
public BasicVehicleDetailType? Vehicle { get; init; }
|
||||||
|
public BasicVehicleDetailType? Trailer { get; init; }
|
||||||
|
public string? CarrierName { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Egy fél (feladó/címzett) normalizálatlan adatai a NAV seller/destination mezőkhöz ÉS a kötelezettség
|
||||||
|
/// országkód-vizsgálatához. (A cím egysoros; a tagolt fel-/lerakodási helyet a <see cref="EkaerConsignment.LoadLocation"/>
|
||||||
|
/// / <see cref="EkaerConsignment.UnloadLocation"/> hordozza.)</summary>
|
||||||
|
public sealed class EkaerEndpoint
|
||||||
|
{
|
||||||
|
public string? Name { get; init; }
|
||||||
|
public string? VatNumber { get; init; }
|
||||||
|
public string? CountryCode { get; init; }
|
||||||
|
public string? FullAddress { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Egy normalizált tétel. A <see cref="WeightKg"/> és a <see cref="ValueHuf"/> már KISZÁMOLT (HUF-ban),
|
||||||
|
/// hogy a küszöb-summa és a tradeCard ugyanazt használja (egyetlen érték-forrás).</summary>
|
||||||
|
public sealed class EkaerLine
|
||||||
|
{
|
||||||
|
public string ExternalId { get; init; } = string.Empty;
|
||||||
|
public string? Vtsz { get; init; }
|
||||||
|
public string? Name { get; init; }
|
||||||
|
public double WeightKg { get; init; }
|
||||||
|
public long? ValueHuf { get; init; }
|
||||||
|
public TradeReasonType TradeReason { get; init; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
using AyCode.Services.Nav.Ekaer;
|
||||||
|
|
||||||
|
namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
|
/// <summary>Egy szállítmány EKÁER bejelentés-kötelezettsége.</summary>
|
||||||
|
public enum EkaerObligation
|
||||||
|
{
|
||||||
|
/// <summary>Kötelező bejelenteni (sort kell létrehozni).</summary>
|
||||||
|
Required,
|
||||||
|
/// <summary>Nem kötelező (belföld, küszöb alatt) — NEM hiba, nincs üzenet.</summary>
|
||||||
|
NotRequired,
|
||||||
|
/// <summary>A döntés nem hozható meg adathiány/formátumhiba miatt (pl. érvénytelen országkód) — NINCS sor, az okok az üzenetekben.</summary>
|
||||||
|
DataError,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A kötelezettség-kiértékelés eredménye: a döntés + (csak DataError esetén) a felhasználónak szóló okok.</summary>
|
||||||
|
public sealed class EkaerObligationResult
|
||||||
|
{
|
||||||
|
public EkaerObligation Obligation { get; private init; }
|
||||||
|
public IReadOnlyList<string> Errors { get; private init; } = [];
|
||||||
|
|
||||||
|
public static EkaerObligationResult Required { get; } = new() { Obligation = EkaerObligation.Required };
|
||||||
|
public static EkaerObligationResult NotRequired { get; } = new() { Obligation = EkaerObligation.NotRequired };
|
||||||
|
public static EkaerObligationResult DataError(IReadOnlyList<string> errors) =>
|
||||||
|
new() { Obligation = EkaerObligation.DataError, Errors = errors };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EKÁER bejelentés-kötelezettség eldöntése egy normalizált <see cref="EkaerConsignment"/>-re — KÖZÖS a bejövő
|
||||||
|
/// (document-csoport) és a kimenő (order) ágon. A NAV mindkét irányban büntet (a felesleges bejelentés ÉS a kimaradás
|
||||||
|
/// is bírság), ezért: a bizonytalan adatot (érvénytelen országkód) NEM döntjük el magától → <see cref="EkaerObligation.DataError"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Sorrend: (1) az AGGREGÁLT tömeg/érték küszöb FELETT → Required, az országkódtól FÜGGETLENÜL — belföld+küszöb
|
||||||
|
/// felett és külföld egyaránt kötelező; az országkód-hibát ilyenkor a generate-validálás jelzi, a sor létrejön;
|
||||||
|
/// (2) küszöb ALATT → itt számít az ország: hiányzó/érvénytelen országkód → DataError (a foreign-vs-belföld nem
|
||||||
|
/// dönthető el); (3) küszöb alatt, érvényes országkódok: külföld (eltér) → Required, belföld (egyezik) → NotRequired.
|
||||||
|
/// A 13/2020. (XII. 23.) PM rendelet alapján a küszöb feladó→címzett→jármű relációra aggregált — a hívó már
|
||||||
|
/// így állítja össze a <see cref="EkaerConsignment.Lines"/>-t (bejövőnél a (Shipping, Partner) csoport tételei).
|
||||||
|
/// </remarks>
|
||||||
|
public static class EkaerReportability
|
||||||
|
{
|
||||||
|
public static EkaerObligationResult Evaluate(EkaerConsignment consignment, IEkaerSettings settings)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(consignment);
|
||||||
|
ArgumentNullException.ThrowIfNull(settings);
|
||||||
|
|
||||||
|
// (1) Küszöb FELETT → kötelező, FÜGGETLENÜL az országkódtól: belföld+küszöb felett ÉS külföld egyaránt kötelező.
|
||||||
|
// Az országkód-hibát ilyenkor NEM itt blokkoljuk — a sor létrejön, a hibát a generate-validálás jelzi.
|
||||||
|
var totalWeight = consignment.Lines.Sum(l => l.WeightKg);
|
||||||
|
var totalValueHuf = consignment.Lines.Sum(l => l.ValueHuf ?? 0L);
|
||||||
|
if (totalWeight >= settings.ThresholdWeightKg || totalValueHuf >= settings.ThresholdValueHuf)
|
||||||
|
return EkaerObligationResult.Required;
|
||||||
|
|
||||||
|
// (2) Küszöb ALATT: itt MÁR az ország dönt — csak a külföldi (eltérő országkód) reláció kötelező. Ehhez érvényes
|
||||||
|
// ISO-2 országkódok kellenek; hiányzó/érvénytelen → a foreign-vs-belföld nem dönthető el → DataError.
|
||||||
|
var subject = Subject(consignment);
|
||||||
|
var errors = new List<string>();
|
||||||
|
if (!IsValidCountry(consignment.Seller.CountryCode))
|
||||||
|
errors.Add($"{subject}: a feladó országkódja hiányzik vagy érvénytelen ('{consignment.Seller.CountryCode}').");
|
||||||
|
if (!IsValidCountry(consignment.Buyer.CountryCode))
|
||||||
|
errors.Add($"{subject}: a címzett országkódja hiányzik vagy érvénytelen ('{consignment.Buyer.CountryCode}').");
|
||||||
|
if (errors.Count > 0)
|
||||||
|
return EkaerObligationResult.DataError(errors);
|
||||||
|
|
||||||
|
// (3) Küszöb alatt, érvényes országkódok: külföld (eltér) → kötelező, belföld (egyezik) → nem kötelező (üzenet nélkül).
|
||||||
|
return CountryEquals(consignment.Seller.CountryCode, consignment.Buyer.CountryCode)
|
||||||
|
? EkaerObligationResult.NotRequired
|
||||||
|
: EkaerObligationResult.Required;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Érvényes EKÁER országkód: pontosan 2 ASCII betű (ISO-2). Üres / más hosszúságú → érvénytelen.</summary>
|
||||||
|
private static bool IsValidCountry(string? code)
|
||||||
|
{
|
||||||
|
var c = code?.Trim();
|
||||||
|
return c is { Length: 2 } && c.All(char.IsAsciiLetter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CountryEquals(string? a, string? b) => string.Equals(a?.Trim(), b?.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static string Subject(EkaerConsignment c)
|
||||||
|
=> c.IsOutgoing ? $"Rendelés #{c.ForeignKey}" : string.IsNullOrWhiteSpace(c.Seller.Name) ? $"Szállítólevél #{c.ForeignKey}" : c.Seller.Name!;
|
||||||
|
}
|
||||||
|
|
@ -37,4 +37,17 @@ public interface IShippingToEkaerMapper
|
||||||
/// a kimenő fuvar-adat (OrderDto) — amíg az nincs bekötve, üresen marad (a validátor jelzi).
|
/// a kimenő fuvar-adat (OrderDto) — amíg az nincs bekötve, üresen marad (a validátor jelzi).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TradeCardType MapOrder(OrderDto order, EkaerCompanyInfo company);
|
TradeCardType MapOrder(OrderDto order, EkaerCompanyInfo company);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bejövő forrás → normalizált <see cref="EkaerConsignment"/>. Egy vagy több <see cref="ShippingDocument"/>-tel
|
||||||
|
/// hívható: EGY dokumentummal a tradeCard-generáláshoz, a (Shipping, Partner) CSOPORTtal a kötelezettség-summához
|
||||||
|
/// (a tételek összevonva). A hívó gondoskodik róla, hogy a csoport azonos partneré és azonos Shippingé legyen.
|
||||||
|
/// </summary>
|
||||||
|
EkaerConsignment ToConsignment(IReadOnlyCollection<ShippingDocument> documents, EkaerCompanyInfo company);
|
||||||
|
|
||||||
|
/// <summary>Kimenő forrás → normalizált <see cref="EkaerConsignment"/> (egy rendelés = egy szállítmány).</summary>
|
||||||
|
EkaerConsignment ToConsignment(OrderDto order, EkaerCompanyInfo company);
|
||||||
|
|
||||||
|
/// <summary>Normalizált szállítmány → NAV tradeCard. KÖZÖS a két irányra; a <c>MapDocument</c>/<c>MapOrder</c> ezt hívja.</summary>
|
||||||
|
TradeCardType BuildTradeCard(EkaerConsignment consignment);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,19 @@ namespace FruitBank.Common.Services.Ekaer;
|
||||||
|
|
||||||
/// <inheritdoc cref="IShippingToEkaerMapper"/>
|
/// <inheritdoc cref="IShippingToEkaerMapper"/>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Tiszta (állapotmentes) leképező. A feladót és a saját céget egységesen <see cref="ICompanyInfoBase"/>-ként kezeli.
|
/// Tiszta (állapotmentes) leképező. A bejövő (<c>ShippingDocument</c>-csoport) és a kimenő (<c>OrderDto</c>) forrás
|
||||||
/// A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be a <c>Models.TradeCardType</c> osztály
|
/// előbb egy irány-független <see cref="EkaerConsignment"/>-re képződik (<see cref="ToConsignment(IReadOnlyCollection{ShippingDocument}, EkaerCompanyInfo)"/>
|
||||||
/// és a <c>Models.Common.TradeCardType</c> enum névütközése miatt.
|
/// / <see cref="ToConsignment(OrderDto, EkaerCompanyInfo)"/>), majd EBBŐL épül a NAV tradeCard (<see cref="BuildTradeCard"/>).
|
||||||
|
/// Így az irányfüggő tudás a két adapterre szorul, a NAV-build közös. A fel-/lerakodási hely és a jármű már a NAV-típus
|
||||||
|
/// (a saját telephely kész <see cref="LocationType"/>, mezővesztés nélkül átengedve); a feladó/címzett normalizálása a
|
||||||
|
/// build-ben történik. A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be a névütközés miatt.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
{
|
{
|
||||||
/// <summary>A NAV EKÁER magyar rendszer — a „belföld" mindig HU; minden más feladó-ország import.</summary>
|
/// <summary>A NAV EKÁER magyar rendszer — a „belföld" alapértéke HU (a vevő-ország feloldásáig).</summary>
|
||||||
private const string HomeCountry = "HU";
|
private const string HomeCountry = "HU";
|
||||||
|
|
||||||
/// <summary>Kimenő pénznem — jelenleg minden HUF (a vevő/rendelés devizája az OrderDto-ban még nincs leképezve;
|
/// <summary>Kimenő pénznem — jelenleg minden HUF (a vevő/rendelés devizája az OrderDto-ban még nincs leképezve).</summary>
|
||||||
/// bekötéskor innen jön). HUF → nincs átváltás (rate 1).</summary>
|
|
||||||
private const string OutboundCurrency = "HUF";
|
private const string OutboundCurrency = "HUF";
|
||||||
|
|
||||||
private readonly IEkaerSettings _settings;
|
private readonly IEkaerSettings _settings;
|
||||||
|
|
@ -30,6 +32,8 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
public ShippingToEkaerMapper(IEkaerSettings settings)
|
public ShippingToEkaerMapper(IEkaerSettings settings)
|
||||||
=> _settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
=> _settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||||
|
|
||||||
|
// ── Belépési pontok (vékony wrapperek a köztes modell + a közös build köré) ───────────────────
|
||||||
|
|
||||||
public IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create)
|
public IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(shipping);
|
ArgumentNullException.ThrowIfNull(shipping);
|
||||||
|
|
@ -41,13 +45,13 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
// Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
|
// Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
|
||||||
foreach (var document in shipping.ShippingDocuments ?? [])
|
foreach (var document in shipping.ShippingDocuments ?? [])
|
||||||
{
|
{
|
||||||
|
document.Shipping ??= shipping; // a vontató/fuvarozó a Shippingről jön
|
||||||
index++;
|
index++;
|
||||||
var rateToHuf = EkaerValueCalculator.ResolveRateToHuf(document.Partner?.Currency, _settings.EurHufRate);
|
|
||||||
operations.Add(new TradeCardOperationType
|
operations.Add(new TradeCardOperationType
|
||||||
{
|
{
|
||||||
Index = index,
|
Index = index,
|
||||||
Operation = operation,
|
Operation = operation,
|
||||||
TradeCard = BuildTradeCard(shipping, document, company, rateToHuf),
|
TradeCard = BuildTradeCard(ToConsignment([document], company)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,130 +62,184 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(document);
|
ArgumentNullException.ThrowIfNull(document);
|
||||||
ArgumentNullException.ThrowIfNull(company);
|
ArgumentNullException.ThrowIfNull(company);
|
||||||
|
return BuildTradeCard(ToConsignment([document], company));
|
||||||
var rateToHuf = EkaerValueCalculator.ResolveRateToHuf(document.Partner?.Currency, _settings.EurHufRate);
|
|
||||||
return BuildTradeCard(document.Shipping, document, company, rateToHuf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public TradeCardType MapOrder(OrderDto order, EkaerCompanyInfo company)
|
public TradeCardType MapOrder(OrderDto order, EkaerCompanyInfo company)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(order);
|
||||||
|
ArgumentNullException.ThrowIfNull(company);
|
||||||
|
return BuildTradeCard(ToConsignment(order, company));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Adapterek: forrás → normalizált EkaerConsignment (az EGYETLEN irányfüggő rész) ────────────
|
||||||
|
|
||||||
|
/// <summary>Bejövő: egy vagy több <c>ShippingDocument</c> (a hívó (Shipping, Partner)-re csoportosít) → egy
|
||||||
|
/// szállítmány a csoport ÖSSZES tételével. Egy dokumentummal a generáláshoz, a csoporttal a kötelezettség-summához.</summary>
|
||||||
|
public EkaerConsignment ToConsignment(IReadOnlyCollection<ShippingDocument> documents, EkaerCompanyInfo company)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(documents);
|
||||||
|
ArgumentNullException.ThrowIfNull(company);
|
||||||
|
if (documents.Count == 0) throw new ArgumentException("Legalább egy ShippingDocument szükséges.", nameof(documents));
|
||||||
|
|
||||||
|
var first = documents.First();
|
||||||
|
var partner = first.Partner; // a csoport azonos partneré (a hívó (Shipping, Partner)-re csoportosít)
|
||||||
|
var shipping = first.Shipping; // azonos Shipping; a kapunál lehet null (a jármű ott nem kell)
|
||||||
|
var rateToHuf = SafeRateToHuf(partner?.Currency);
|
||||||
|
|
||||||
|
var lines = documents
|
||||||
|
.SelectMany(d => d.ShippingItems ?? [])
|
||||||
|
.Select(item => new EkaerLine
|
||||||
|
{
|
||||||
|
ExternalId = item.Id.ToString(),
|
||||||
|
TradeReason = TradeReasonType.A, // bejövő áru = beszerzés
|
||||||
|
Vtsz = NormalizeVtsz(item.ProductDto?.Gtin),
|
||||||
|
Name = item.ProductName,
|
||||||
|
WeightKg = item.MeasuredGrossWeight,
|
||||||
|
ValueHuf = EkaerValueCalculator.ItemValueHuf(item, rateToHuf),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new EkaerConsignment
|
||||||
|
{
|
||||||
|
ForeignKey = first.Id,
|
||||||
|
IsOutgoing = false,
|
||||||
|
Currency = partner?.Currency,
|
||||||
|
Seller = PartnerEndpoint(partner),
|
||||||
|
Buyer = CompanyEndpoint(company),
|
||||||
|
LoadLocation = BuildLocation(partner), // a beszállító telephelye (a PartnerDepot-bekötés külön feladat)
|
||||||
|
UnloadLocation = company.Site, // a saját telephely (kész LocationType — mezővesztés nélkül átengedve)
|
||||||
|
Lines = lines,
|
||||||
|
Vehicle = BuildVehicle(shipping?.CargoTruck),
|
||||||
|
Trailer = BuildVehicle(shipping?.CargoTrailer),
|
||||||
|
CarrierName = shipping?.CargoPartner?.Name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Kimenő: egy rendelés → egy szállítmány. Nincs Shipping; a kötelezettséget a rendelésre nézzük.</summary>
|
||||||
|
public EkaerConsignment ToConsignment(OrderDto order, EkaerCompanyInfo company)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(order);
|
ArgumentNullException.ThrowIfNull(order);
|
||||||
ArgumentNullException.ThrowIfNull(company);
|
ArgumentNullException.ThrowIfNull(company);
|
||||||
|
|
||||||
var customer = order.Customer;
|
var customer = order.Customer;
|
||||||
var rateToHuf = EkaerValueCalculator.ResolveRateToHuf(OutboundCurrency, _settings.EurHufRate);
|
var rateToHuf = SafeRateToHuf(OutboundCurrency); // jelenleg minden HUF
|
||||||
|
|
||||||
var tradeCard = new TradeCardType
|
var lines = (order.OrderItemDtos ?? [])
|
||||||
|
.Select(item => new EkaerLine
|
||||||
|
{
|
||||||
|
ExternalId = item.Id.ToString(),
|
||||||
|
TradeReason = TradeReasonType.S, // kimenő áru = értékesítés
|
||||||
|
Vtsz = NormalizeVtsz(item.ProductDto?.Gtin),
|
||||||
|
Name = item.ProductDto?.Name,
|
||||||
|
WeightKg = item.GrossWeight,
|
||||||
|
ValueHuf = EkaerValueCalculator.ItemValueHuf(item, rateToHuf),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new EkaerConsignment
|
||||||
{
|
{
|
||||||
// Kimenő belföldi értékesítés. (Export `E` + külföldi deviza: későbbi — jelenleg minden HUF/belföld.)
|
ForeignKey = order.Id,
|
||||||
TradeType = TradeType.D,
|
IsOutgoing = true,
|
||||||
ModByCarrierEnabled = false,
|
Currency = OutboundCurrency,
|
||||||
|
Seller = CompanyEndpoint(company),
|
||||||
// Eladó = mi (kimenő relációban)
|
Buyer = CustomerEndpoint(customer),
|
||||||
SellerName = company.Name,
|
|
||||||
SellerVatNumber = NormalizeVatNumber(company.TaxId),
|
|
||||||
SellerCountry = NormalizeCountryCode(company.CountryCode, 2),
|
|
||||||
SellerAddress = Truncate(company.FullAddress, 200),
|
|
||||||
|
|
||||||
// Címzett = a vevő. Ország jelenleg HU (belföld); a Customer.CountryId-feloldás + export: későbbi.
|
|
||||||
DestinationName = customer?.Company,
|
|
||||||
DestinationVatNumber = NormalizeVatNumber(customer?.VatNumber),
|
|
||||||
DestinationCountry = HomeCountry,
|
|
||||||
DestinationAddress = Truncate(ComposeCustomerAddress(customer), 200),
|
|
||||||
|
|
||||||
// Felrakodás = a mi telephelyünk; lerakodás = a vevő.
|
|
||||||
LoadLocation = company.Site,
|
LoadLocation = company.Site,
|
||||||
UnloadLocation = BuildCustomerLocation(customer),
|
UnloadLocation = BuildCustomerLocation(customer),
|
||||||
|
Lines = lines,
|
||||||
// Vonó jármű / fuvarozó: a kimenő fuvar-adat (OrderDto) bekötéséig nincs forrás — üresen marad,
|
// Kimenőnél a VEVŐ veszi át / viszi el az árut → ő a fuvarozó.
|
||||||
// a validátor jelzi (mint a bejövőnél kezdetben). TODO: order.<transport> → Vehicle / CarrierText.
|
CarrierName = customer?.Company,
|
||||||
|
// A vonó jármű (rendszám) a customer-hez még nincs bekötve → üresen marad, a felrakodás megkezdéséig pótolandó
|
||||||
|
// (a validátor warningolja). Amint bekötik, a Vehicle is innen jön.
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var item in order.OrderItemDtos ?? []) tradeCard.Items.Add(BuildOutboundItem(item, rateToHuf));
|
|
||||||
return tradeCard;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TradeCardType BuildTradeCard(Shipping? shipping, ShippingDocument document, EkaerCompanyInfo company, double rateToHuf)
|
// ── Közös build: normalizált szállítmány → NAV tradeCard (a korábbi két ág helyett egy) ───────
|
||||||
|
|
||||||
|
public TradeCardType BuildTradeCard(EkaerConsignment consignment)
|
||||||
{
|
{
|
||||||
var seller = document.Partner; // a beszállító (feladó) — ICompanyInfoBase
|
ArgumentNullException.ThrowIfNull(consignment);
|
||||||
|
|
||||||
var tradeCard = new TradeCardType
|
var tradeCard = new TradeCardType
|
||||||
{
|
{
|
||||||
TradeType = ResolveTradeType(seller),
|
TradeType = ResolveTradeType(consignment),
|
||||||
ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthat
|
ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthat
|
||||||
|
|
||||||
// Feladó / eladó = a beszállító
|
SellerName = consignment.Seller.Name,
|
||||||
SellerName = seller?.Name,
|
SellerVatNumber = NormalizeVatNumber(consignment.Seller.VatNumber),
|
||||||
SellerVatNumber = NormalizeVatNumber(seller?.TaxId),
|
SellerCountry = NormalizeCountryCode(consignment.Seller.CountryCode, 2),
|
||||||
SellerCountry = NormalizeCountryCode(seller?.CountryCode, 2),
|
SellerAddress = Truncate(consignment.Seller.FullAddress, 200),
|
||||||
SellerAddress = Truncate(seller?.FullAddress, 200),
|
|
||||||
|
|
||||||
// Címzett = a bejelentő saját cége (bejövő relációban)
|
DestinationName = consignment.Buyer.Name,
|
||||||
DestinationName = company.Name,
|
DestinationVatNumber = NormalizeVatNumber(consignment.Buyer.VatNumber),
|
||||||
DestinationVatNumber = NormalizeVatNumber(company.TaxId),
|
DestinationCountry = NormalizeCountryCode(consignment.Buyer.CountryCode, 2),
|
||||||
DestinationCountry = NormalizeCountryCode(company.CountryCode, 2),
|
DestinationAddress = Truncate(consignment.Buyer.FullAddress, 200),
|
||||||
DestinationAddress = Truncate(company.FullAddress, 200),
|
|
||||||
|
|
||||||
// Fuvarozó (Shipping.CargoPartner). Regisztrált EKAER-azonosító nincs, csak szöveges név.
|
CarrierText = consignment.CarrierName,
|
||||||
CarrierText = shipping?.CargoPartner?.Name,
|
|
||||||
|
|
||||||
// Lerakodás = saját telephely (a cégadatból); felrakodás = a beszállító telephelye.
|
// A helyek kész NAV LocationType-ok (a saját telephely érintetlenül) — átengedve.
|
||||||
UnloadLocation = company.Site,
|
LoadLocation = consignment.LoadLocation,
|
||||||
LoadLocation = BuildLoadLocation(seller),
|
UnloadLocation = consignment.UnloadLocation,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vonó jármű + vontatmány: az EKÁER külön bejegyzésként kéri (vehicle / vehicle2).
|
if (consignment.Vehicle != null) tradeCard.Vehicle = consignment.Vehicle;
|
||||||
if (shipping?.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck);
|
if (consignment.Trailer != null) tradeCard.Vehicle2 = consignment.Trailer;
|
||||||
if (shipping?.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer);
|
|
||||||
|
|
||||||
foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item, rateToHuf));
|
foreach (var line in consignment.Lines) tradeCard.Items.Add(BuildItem(line));
|
||||||
return tradeCard;
|
return tradeCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Belföld (a feladó és a címzett országkódja megegyezik) → <c>D</c>; eltérő országok → kimenőnél
|
||||||
/// Belföldi feladó (HU) → <c>D</c> (belföld-belföld), egyébként → <c>I</c> (import). A NAV EKÁER magyar,
|
/// export (<c>E</c>), bejövőnél import (<c>I</c>).</summary>
|
||||||
/// így a belföld mindig HU; az export (<c>E</c>) jelenleg nincs leképezve (lásd EKAER_TODO #1, #7).
|
private static TradeType ResolveTradeType(EkaerConsignment c)
|
||||||
/// </summary>
|
|
||||||
private static TradeType ResolveTradeType(ICompanyInfoBase? seller)
|
|
||||||
=> string.Equals(seller?.CountryCode, HomeCountry, StringComparison.OrdinalIgnoreCase)
|
|
||||||
? TradeType.D
|
|
||||||
: TradeType.I;
|
|
||||||
|
|
||||||
private static TradeCardItemType BuildItem(ShippingItem item, double rateToHuf) => new()
|
|
||||||
{
|
{
|
||||||
ItemExternalId = item.Id.ToString(),
|
if (string.Equals(c.Seller.CountryCode?.Trim(), c.Buyer.CountryCode?.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||||
// Bejövő áru = beszerzés → A. (Enum: S=értékesítés, A=beszerzés, W=bérmunka, O=egyéb.) Lásd EKAER_TODO #9.
|
return TradeType.D;
|
||||||
TradeReason = TradeReasonType.A,
|
return c.IsOutgoing ? TradeType.E : TradeType.I;
|
||||||
ProductVtsz = NormalizeVtsz(item.ProductDto?.Gtin), // VTSZ — átmenetileg a Gtin oszlopban (FBANKAPP-DMODEL-I-P6X4)
|
}
|
||||||
ProductName = item.ProductName,
|
|
||||||
Weight = (decimal)item.MeasuredGrossWeight, // bruttó tömeg kg-ban (lásd EKAER_TODO #4)
|
private static TradeCardItemType BuildItem(EkaerLine line) => new()
|
||||||
Value = EkaerValueCalculator.ItemValueHuf(item, rateToHuf), // beszerzési érték HUF-ban (Partner.Currency → árfolyam); 0/ismeretlen → null
|
{
|
||||||
|
ItemExternalId = line.ExternalId,
|
||||||
|
TradeReason = line.TradeReason,
|
||||||
|
ProductVtsz = line.Vtsz, // már normalizált (az adapterben)
|
||||||
|
ProductName = line.Name,
|
||||||
|
Weight = (decimal)line.WeightKg,
|
||||||
|
Value = line.ValueHuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Kimenő (Order) tétel → tradeCardItem. Értékesítés (<c>S</c>); bruttó tömeg a palettákból; nettó érték HUF-ban.</summary>
|
// ── Forrás → normalizálatlan végpont (feladó/címzett: név/adószám/ország/egysoros cím) ─────────
|
||||||
private static TradeCardItemType BuildOutboundItem(OrderItemDto item, double rateToHuf) => new()
|
|
||||||
|
private static EkaerEndpoint PartnerEndpoint(ICompanyInfoBase? partner) => new()
|
||||||
{
|
{
|
||||||
ItemExternalId = item.Id.ToString(),
|
Name = partner?.Name,
|
||||||
// Kimenő áru = értékesítés → S. (Enum: S=értékesítés, A=beszerzés, W=bérmunka, O=egyéb.)
|
VatNumber = partner?.TaxId,
|
||||||
TradeReason = TradeReasonType.S,
|
CountryCode = partner?.CountryCode,
|
||||||
ProductVtsz = NormalizeVtsz(item.ProductDto?.Gtin), // VTSZ — átmenetileg a Gtin oszlopban (FBANKAPP-DMODEL-I-P6X4)
|
FullAddress = partner?.FullAddress,
|
||||||
ProductName = item.ProductDto?.Name,
|
|
||||||
Weight = (decimal)item.GrossWeight, // bruttó tömeg kg-ban (OrderItemPallets összegéből)
|
|
||||||
Value = EkaerValueCalculator.ItemValueHuf(item, rateToHuf), // nettó értékesítési érték HUF-ban; 0/ismeretlen → null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static BasicVehicleDetailType BuildVehicle(CargoTruck truck) => new()
|
private static EkaerEndpoint CompanyEndpoint(EkaerCompanyInfo company) => new()
|
||||||
{
|
{
|
||||||
PlateNumber = NormalizePlateNumber(truck.LicencePlate),
|
Name = company.Name,
|
||||||
Country = NormalizeCountryCode(truck.CountryCode, 3),
|
VatNumber = company.TaxId,
|
||||||
|
CountryCode = company.CountryCode,
|
||||||
|
FullAddress = company.FullAddress,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>A vevő mint fél. Ország jelenleg HU — a Customer.CountryId→ISO feloldás (export E) külön feladat.</summary>
|
||||||
/// Felrakodási hely a beszállító adataiból. (Magyar feladónál a NAV a Phone/Email-t is kéri, ami az
|
private static EkaerEndpoint CustomerEndpoint(Customer? customer) => new()
|
||||||
/// entitásban nincs — lásd EKAER_TODO #6.)
|
{
|
||||||
/// </summary>
|
Name = customer?.Company,
|
||||||
private static LocationType? BuildLoadLocation(ICompanyInfoBase? seller)
|
VatNumber = customer?.VatNumber,
|
||||||
|
CountryCode = HomeCountry,
|
||||||
|
FullAddress = ComposeCustomerAddress(customer),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Forrás → NAV LocationType / jármű (a saját telephelyet az adapter közvetlenül adja) ────────
|
||||||
|
|
||||||
|
/// <summary>Felrakodási hely a beszállító adataiból (bejövő). A NAV magyar feladónál a Phone/Email-t is kéri, ami
|
||||||
|
/// az entitásban nincs — lásd EKAER_TODO #6.</summary>
|
||||||
|
private static LocationType? BuildLocation(ICompanyInfoBase? seller)
|
||||||
{
|
{
|
||||||
if (seller is null) return null;
|
if (seller is null) return null;
|
||||||
return new LocationType
|
return new LocationType
|
||||||
|
|
@ -195,16 +253,7 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>A vevő egysoros címe (irsz + város + utca) a NAV <c>destinationAddress</c>-hez.</summary>
|
/// <summary>Lerakodási hely a vevő adataiból (kimenő). Ország jelenleg HU (belföld).</summary>
|
||||||
private static string? ComposeCustomerAddress(Customer? customer)
|
|
||||||
{
|
|
||||||
if (customer is null) return null;
|
|
||||||
var parts = new[] { customer.ZipPostalCode, customer.City, customer.StreetAddress, customer.StreetAddress2 }
|
|
||||||
.Where(p => !string.IsNullOrWhiteSpace(p));
|
|
||||||
return EmptyToNull(string.Join(" ", parts).Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Lerakodási hely a vevő adataiból (kimenő reláció). Ország jelenleg HU (belföld).</summary>
|
|
||||||
private static LocationType? BuildCustomerLocation(Customer? customer)
|
private static LocationType? BuildCustomerLocation(Customer? customer)
|
||||||
{
|
{
|
||||||
if (customer is null) return null;
|
if (customer is null) return null;
|
||||||
|
|
@ -219,6 +268,33 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BasicVehicleDetailType? BuildVehicle(CargoTruck? truck)
|
||||||
|
{
|
||||||
|
if (truck is null) return null;
|
||||||
|
return new BasicVehicleDetailType
|
||||||
|
{
|
||||||
|
PlateNumber = NormalizePlateNumber(truck.LicencePlate),
|
||||||
|
Country = NormalizeCountryCode(truck.CountryCode, 3),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A számla-pénznem → HUF szorzó, NEM dobó változat (a kapu külföldinél küszöb nélkül jelent, ott az érték
|
||||||
|
/// nem kell): HUF → 1; külföldi + érvényes árfolyam → árfolyam; külföldi + hiányzó árfolyam → 0 (a tétel-érték null lesz).
|
||||||
|
/// Generáláskor a service config-kapuja (TryConfigError) előbb elvágja a hiányzó-árfolyamos külföldi esetet.</summary>
|
||||||
|
private double SafeRateToHuf(string? currency)
|
||||||
|
=> EkaerValueCalculator.IsHuf(currency) ? 1d : (_settings.EurHufRate > 0 ? _settings.EurHufRate : 0d);
|
||||||
|
|
||||||
|
/// <summary>A vevő egysoros címe (irsz + város + utca) a NAV <c>destinationAddress</c>-hez.</summary>
|
||||||
|
private static string? ComposeCustomerAddress(Customer? customer)
|
||||||
|
{
|
||||||
|
if (customer is null) return null;
|
||||||
|
var parts = new[] { customer.ZipPostalCode, customer.City, customer.StreetAddress, customer.StreetAddress2 }
|
||||||
|
.Where(p => !string.IsNullOrWhiteSpace(p));
|
||||||
|
return EmptyToNull(string.Join(" ", parts).Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Normalizálók (NAV pattern-ek) ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>Adószám normalizálása. Pattern: <c>[0-9A-Z-]{1,15}</c>.</summary>
|
/// <summary>Adószám normalizálása. Pattern: <c>[0-9A-Z-]{1,15}</c>.</summary>
|
||||||
private static string? NormalizeVatNumber(string? value)
|
private static string? NormalizeVatNumber(string? value)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
using FruitBank.Common.Services.Ekaer;
|
||||||
|
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Shared.Tests.Ekaer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tesztek a <see cref="EkaerReportability"/> bejelentés-kötelezettség-eldöntésére. Tisztán memóriában felépített
|
||||||
|
/// <see cref="EkaerConsignment"/>-eken fut (nincs hálózat/DB), determinisztikus. Küszöb: 200 kg / 250 000 Ft.
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public sealed class EkaerReportabilityTests
|
||||||
|
{
|
||||||
|
private static readonly EkaerSettings Settings = new()
|
||||||
|
{
|
||||||
|
EurHufRate = 356,
|
||||||
|
ThresholdWeightKg = 200,
|
||||||
|
ThresholdValueHuf = 250_000,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static EkaerConsignment Consignment(string? sellerCountry, string? buyerCountry, params EkaerLine[] lines) => new()
|
||||||
|
{
|
||||||
|
Seller = new EkaerEndpoint { Name = "Feladó", CountryCode = sellerCountry },
|
||||||
|
Buyer = new EkaerEndpoint { Name = "Címzett", CountryCode = buyerCountry },
|
||||||
|
Lines = lines,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static EkaerLine Line(double weightKg, long? valueHuf) =>
|
||||||
|
new() { ExternalId = "1", WeightKg = weightKg, ValueHuf = valueHuf, TradeReason = TradeReasonType.A };
|
||||||
|
|
||||||
|
// ---- Belföld + küszöb ----------------------------------------------------
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Domestic_BelowBothThresholds_NotRequired()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(100, 100_000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.NotRequired, result.Obligation);
|
||||||
|
Assert.AreEqual(0, result.Errors.Count, "küszöb alatt NINCS üzenet (nem hiba)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Domestic_WeightAtOrOverThreshold_Required()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(200, 0)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation, "a tömeg eléri a küszöböt → kötelező (VAGY-logika)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Domestic_ValueOverThreshold_Required()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(10, 300_000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation, "az érték átlépi a küszöböt → kötelező");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Domestic_AggregatedOverThreshold_Required()
|
||||||
|
{
|
||||||
|
// Egyenként 200 kg ALATT, EGYÜTT fölötte → kötelező. Ez a (Shipping, Partner)-aggregálás lényege.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(120, 0), Line(120, 0)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Külföld (a két országkód eltér) → küszöb nélkül kötelező ------------
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CrossBorder_BelowThreshold_StillRequired()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("DE", "HU", Line(1, 1)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation, "eltérő országkód → mindig kötelező, küszöb nélkül");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SameForeignCountryBothEnds_TreatedAsDomesticThreshold()
|
||||||
|
{
|
||||||
|
// Mindkét vég azonos (nem HU) ország → NEM határátlépő → a küszöb dönt.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("DE", "DE", Line(1, 1)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.NotRequired, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Adathiba: érvénytelen/hiányzó országkód → DataError CSAK küszöb alatt ----
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BelowThreshold_MissingSellerCountry_DataError()
|
||||||
|
{
|
||||||
|
// Küszöb alatt MÁR számít az ország (foreign-vs-belföld) — hiányzó országkód → nem dönthető el → DataError.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment(null, "HU", Line(10, 1000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.DataError, result.Obligation);
|
||||||
|
Assert.IsTrue(result.Errors.Count > 0, "a hiányzó országkódot jelezni kell a felhasználónak");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BelowThreshold_InvalidSellerCountry_NotIso2_DataError()
|
||||||
|
{
|
||||||
|
// Teljes név (nem ISO-2) → érvénytelen → küszöb alatt nem dönthető el → DataError.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("Magyarország", "HU", Line(10, 1000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.DataError, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BelowThreshold_MissingBuyerCountry_DataError()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "", Line(10, 1000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.DataError, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OverThreshold_InvalidCountry_Required_NotDataError()
|
||||||
|
{
|
||||||
|
// Küszöb FELETT (tömeg 500 ≥ 200) a kötelezettség az országkódtól FÜGGETLEN → a sor létrejön (a hibát a
|
||||||
|
// generate-validálás jelzi), NEM DataError.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment(null, "Magyarország", Line(500, 0)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation);
|
||||||
|
Assert.AreEqual(0, result.Errors.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Hibás / üres adatok (robusztusság) ----------------------------------
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NoLines_Domestic_NotRequired()
|
||||||
|
{
|
||||||
|
// Üres szállítmány (nincs tétel) → tömeg/érték 0 → küszöb alatt → belföld → nem kötelező (NEM dob).
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU"), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.NotRequired, result.Obligation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BothCountriesInvalid_BelowThreshold_DataError_WithTwoMessages()
|
||||||
|
{
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment(null, "", Line(10, 1000)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.DataError, result.Obligation);
|
||||||
|
Assert.AreEqual(2, result.Errors.Count, "mindkét hibás országkódot külön jelezni kell");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NullLineValue_HandledAsZero_WeightDecides()
|
||||||
|
{
|
||||||
|
// Hiányzó (null) tétel-érték (pl. külföldi deviza árfolyam nélkül) → 0-ként számít, NEM dob; a tömeg dönt.
|
||||||
|
var result = EkaerReportability.Evaluate(Consignment("HU", "HU", Line(500, null)), Settings);
|
||||||
|
Assert.AreEqual(EkaerObligation.Required, result.Obligation, "a tömeg átlépi a küszöböt — a null érték nem akadály");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -83,6 +83,28 @@ public sealed class ShippingToEkaerMapperTests
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static ShippingDocument CreateInboundDocument(int itemId, double weight, string sellerCountry = "HU") => new()
|
||||||
|
{
|
||||||
|
Country = sellerCountry,
|
||||||
|
Partner = new Partner
|
||||||
|
{
|
||||||
|
Name = "Beszállító Kft",
|
||||||
|
TaxId = "12345678-2-42",
|
||||||
|
CountryCode = sellerCountry,
|
||||||
|
PostalCode = "1011",
|
||||||
|
City = "Budapest",
|
||||||
|
Street = "Fő utca 1",
|
||||||
|
},
|
||||||
|
ShippingItems = [new ShippingItem
|
||||||
|
{
|
||||||
|
Id = itemId,
|
||||||
|
Name = "Alma",
|
||||||
|
ProductDto = new ProductDto { Gtin = "08081010", Name = "Alma" },
|
||||||
|
MeasuredGrossWeight = weight,
|
||||||
|
UnitPriceOnDocument = 5.0,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
// ---- Granularitás / index ----------------------------------------------
|
// ---- Granularitás / index ----------------------------------------------
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -216,4 +238,55 @@ public sealed class ShippingToEkaerMapperTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MapShipping_NullCompany_Throws()
|
public void MapShipping_NullCompany_Throws()
|
||||||
=> Assert.ThrowsExactly<ArgumentNullException>(() => Mapper.MapShipping(CreateShipping(), null!));
|
=> Assert.ThrowsExactly<ArgumentNullException>(() => Mapper.MapShipping(CreateShipping(), null!));
|
||||||
|
|
||||||
|
// ---- Köztes modell: ToConsignment (bejövő) ------------------------------
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_MultipleDocuments_CombinesLinesAndAggregatesWeight()
|
||||||
|
{
|
||||||
|
// Két szállítólevél (azonos partner, azonos Shipping) → egy szállítmány, MINDKÉT tétellel — a küszöb-aggregáláshoz.
|
||||||
|
var consignment = Mapper.ToConsignment(
|
||||||
|
[CreateInboundDocument(itemId: 1, weight: 100), CreateInboundDocument(itemId: 2, weight: 150)],
|
||||||
|
CreateCompany());
|
||||||
|
|
||||||
|
Assert.AreEqual(2, consignment.Lines.Count, "a csoport ÖSSZES tétele egy szállítmányban");
|
||||||
|
Assert.AreEqual(250d, consignment.Lines.Sum(l => l.WeightKg), 0.001, "a tömeg a dokumentumokból összegződik");
|
||||||
|
Assert.IsFalse(consignment.IsOutgoing);
|
||||||
|
Assert.AreEqual("HU", consignment.Seller.CountryCode, "feladó = a beszállító partner");
|
||||||
|
Assert.AreEqual("HU", consignment.Buyer.CountryCode, "címzett = a saját cég");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_BuildTradeCard_PreservesCompanySiteAsUnloadLocation()
|
||||||
|
{
|
||||||
|
var company = CreateCompany();
|
||||||
|
var tradeCard = Mapper.BuildTradeCard(Mapper.ToConsignment([CreateInboundDocument(1, 100)], company));
|
||||||
|
Assert.AreSame(company.Site, tradeCard.UnloadLocation, "a saját telephely érintetlenül (mezővesztés nélkül) megy át");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_EmptyDocuments_Throws()
|
||||||
|
=> Assert.ThrowsExactly<ArgumentException>(() => Mapper.ToConsignment([], CreateCompany()));
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_NullPartner_DoesNotThrow_SellerCountryNull()
|
||||||
|
{
|
||||||
|
// Hibás adat: nincs Partner a szállítólevélen → az adapter NEM dob; a feladó-ország null (ezt a kötelezettség-
|
||||||
|
// értékelő / generate-validálás kezeli, nem a leképező).
|
||||||
|
var doc = new ShippingDocument { Partner = null, ShippingItems = [new ShippingItem { Id = 1, MeasuredGrossWeight = 50 }] };
|
||||||
|
var consignment = Mapper.ToConsignment([doc], CreateCompany());
|
||||||
|
|
||||||
|
Assert.IsNull(consignment.Seller.CountryCode, "nincs partner → nincs feladó-ország");
|
||||||
|
Assert.AreEqual(1, consignment.Lines.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ToConsignment_Inbound_NullShippingItems_EmptyLines()
|
||||||
|
{
|
||||||
|
// Hibás/hiányos adat: nincs tétel a szállítólevélen → üres Lines, NEM dob.
|
||||||
|
var doc = new ShippingDocument { Partner = new Partner { CountryCode = "HU" }, ShippingItems = null };
|
||||||
|
var consignment = Mapper.ToConsignment([doc], CreateCompany());
|
||||||
|
|
||||||
|
Assert.AreEqual(0, consignment.Lines.Count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,17 @@
|
||||||
@using AyCode.Services.Nav
|
@using AyCode.Services.Nav
|
||||||
@using AyCode.Services.Nav.Ekaer.Models
|
@using AyCode.Services.Nav.Ekaer.Models
|
||||||
@using AyCode.Utils.Extensions
|
@using AyCode.Utils.Extensions
|
||||||
|
@using FruitBank.Common.Dtos
|
||||||
@using FruitBank.Common.Entities
|
@using FruitBank.Common.Entities
|
||||||
@using FruitBankHybrid.Shared.Databases
|
@using FruitBankHybrid.Shared.Databases
|
||||||
|
@using FruitBankHybrid.Shared.Extensions
|
||||||
@using FruitBankHybrid.Shared.Services.Loggers
|
@using FruitBankHybrid.Shared.Services.Loggers
|
||||||
@using FruitBankHybrid.Shared.Services.SignalRs
|
@using FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
|
||||||
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
|
||||||
@inject FruitBankSignalRClient FruitBankSignalRClient
|
@inject FruitBankSignalRClient FruitBankSignalRClient
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
|
||||||
<MgGridWithInfoPanel ShowInfoPanel="@IsMasterGrid">
|
<MgGridWithInfoPanel ShowInfoPanel="@IsMasterGrid">
|
||||||
<GridContent>
|
<GridContent>
|
||||||
|
|
@ -147,6 +150,10 @@
|
||||||
/// hogy a fül-számlálók azonnal frissüljenek, mert a sor átkerülhet másik tabra.</summary>
|
/// hogy a fül-számlálók azonnal frissüljenek, mert a sor átkerülhet másik tabra.</summary>
|
||||||
[Parameter] public EventCallback OnDataChanged { get; set; }
|
[Parameter] public EventCallback OnDataChanged { get; set; }
|
||||||
|
|
||||||
|
/// <summary>A szülő (oldal) értesítése a hosszú művelet (sorok létrehozása) elejéről (true) / végéről (false) —
|
||||||
|
/// hogy az oldal Loading panelje megjelenjen/eltűnjön.</summary>
|
||||||
|
[Parameter] public EventCallback<bool> OnBusyChanged { get; set; }
|
||||||
|
|
||||||
public bool IsMasterGrid => ParentDataItem == null;
|
public bool IsMasterGrid => ParentDataItem == null;
|
||||||
|
|
||||||
private LoggerClient<GridEkaerHistory> _logger;
|
private LoggerClient<GridEkaerHistory> _logger;
|
||||||
|
|
@ -238,13 +245,23 @@
|
||||||
{
|
{
|
||||||
if (_creatingMissing) return;
|
if (_creatingMissing) return;
|
||||||
_creatingMissing = true;
|
_creatingMissing = true;
|
||||||
|
await OnBusyChanged.InvokeAsync(true); // Loading panel BE — a művelet lassú lehet (kimenő ág)
|
||||||
|
|
||||||
|
EkaerCreateResult? result = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var createdCount = await FruitBankSignalRClient.CreateMissingEkaerHistories(CreateFromDate);
|
result = await FruitBankSignalRClient.CreateMissingEkaerHistories(CreateFromDate);
|
||||||
_logger.Info($"CreateMissingEkaerHistories; created: {createdCount}; fromDate: {CreateFromDate:yyyy.MM.dd}");
|
var createdCount = result?.CreatedCount ?? 0;
|
||||||
|
_logger.Info($"CreateMissingEkaerHistories; created: {createdCount}; messages: {result?.Messages.Count ?? 0}; fromDate: {CreateFromDate:yyyy.MM.dd}");
|
||||||
|
|
||||||
if (createdCount > 0) await ReloadDataFromDb(true);
|
foreach (var message in result?.Messages ?? [])
|
||||||
|
_logger.Warning($"CreateMissingEkaerHistories; skipped: {message}");
|
||||||
|
|
||||||
|
if (createdCount > 0)
|
||||||
|
{
|
||||||
|
await ReloadDataFromDb(true);
|
||||||
|
await NotifyDataChanged(); // a fül-számlálók frissítése (új sorok)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -253,7 +270,12 @@
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_creatingMissing = false;
|
_creatingMissing = false;
|
||||||
|
await OnBusyChanged.InvokeAsync(false); // Loading panel KI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Az adathiány/formátumhiba miatt KIHAGYOTT jelöltek üzenetei popupban — ezeket javítani kell, mielőtt bekerülhetnek.
|
||||||
|
if (result?.Messages is { Count: > 0 } skipped)
|
||||||
|
await DialogService.ShowMessageBoxAsync("EKÁER — kihagyott tételek", string.Join(Environment.NewLine, skipped), MessageBoxRenderStyle.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnCopyClick(EkaerHistory ekaerHistory)
|
private async Task OnCopyClick(EkaerHistory ekaerHistory)
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@
|
||||||
|
|
||||||
<DxTabs ActiveTabIndexChanged="(i) => OnActiveTabChanged(i)" RenderMode="TabsRenderMode.OnDemand" AllowTabReorder="false">
|
<DxTabs ActiveTabIndexChanged="(i) => OnActiveTabChanged(i)" RenderMode="TabsRenderMode.OnDemand" AllowTabReorder="false">
|
||||||
<DxTabPage Text="Beküldésre váró">
|
<DxTabPage Text="Beküldésre váró">
|
||||||
<GridEkaerHistory @ref="gridEkaerHistoryPending" Filter="EkaerHistoryFilter.ToSubmit" OnDataChanged="RefreshNeedsCompletionCountAsync"></GridEkaerHistory>
|
<GridEkaerHistory @ref="gridEkaerHistoryPending" Filter="EkaerHistoryFilter.ToSubmit" OnDataChanged="RefreshNeedsCompletionCountAsync" OnBusyChanged="OnGridBusyChanged"></GridEkaerHistory>
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
<DxTabPage Text="@NeedsCompletionTabText">
|
<DxTabPage Text="@NeedsCompletionTabText">
|
||||||
<GridEkaerHistory @ref="gridEkaerHistoryNeedsCompletion" Filter="EkaerHistoryFilter.ToSubmit" OnDataChanged="RefreshNeedsCompletionCountAsync"></GridEkaerHistory>
|
<GridEkaerHistory @ref="gridEkaerHistoryNeedsCompletion" Filter="EkaerHistoryFilter.ToSubmit" OnDataChanged="RefreshNeedsCompletionCountAsync" OnBusyChanged="OnGridBusyChanged"></GridEkaerHistory>
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
<DxTabPage Text="Elküldött">
|
<DxTabPage Text="Elküldött">
|
||||||
<GridEkaerHistory @ref="gridEkaerHistorySent" Filter="EkaerHistoryFilter.Sent" OnDataChanged="RefreshNeedsCompletionCountAsync"></GridEkaerHistory>
|
<GridEkaerHistory @ref="gridEkaerHistorySent" Filter="EkaerHistoryFilter.Sent" OnDataChanged="RefreshNeedsCompletionCountAsync" OnBusyChanged="OnGridBusyChanged"></GridEkaerHistory>
|
||||||
</DxTabPage>
|
</DxTabPage>
|
||||||
</DxTabs>
|
</DxTabs>
|
||||||
</DxLoadingPanel>
|
</DxLoadingPanel>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using AyCode.Core.Loggers;
|
||||||
using FruitBank.Common.Entities;
|
using FruitBank.Common.Entities;
|
||||||
using FruitBank.Common.Models;
|
using FruitBank.Common.Models;
|
||||||
using FruitBankHybrid.Shared.Components.Grids.Ekaers;
|
using FruitBankHybrid.Shared.Components.Grids.Ekaers;
|
||||||
|
using FruitBankHybrid.Shared.Databases;
|
||||||
using FruitBankHybrid.Shared.Services.Loggers;
|
using FruitBankHybrid.Shared.Services.Loggers;
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
using Mango.Nop.Core.Loggers;
|
using Mango.Nop.Core.Loggers;
|
||||||
|
|
@ -45,6 +46,13 @@ public partial class Ekaer : ComponentBase
|
||||||
await RefreshNeedsCompletionCountAsync();
|
await RefreshNeedsCompletionCountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>A grid jelzi a hosszú művelet (sorok létrehozása) elejét (true) / végét (false) → az oldal Loading panelje meg/elrejtve.</summary>
|
||||||
|
private Task OnGridBusyChanged(bool busy)
|
||||||
|
{
|
||||||
|
LoadingPanelVisibility.Visible = busy;
|
||||||
|
return InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>A „Pótlásra váró" rekordok számának frissítése a fül feliratához (a count endpointról, flag-szűrővel).</summary>
|
/// <summary>A „Pótlásra váró" rekordok számának frissítése a fül feliratához (a count endpointról, flag-szűrővel).</summary>
|
||||||
private async Task RefreshNeedsCompletionCountAsync()
|
private async Task RefreshNeedsCompletionCountAsync()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
||||||
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 foreignKey, bool isOutgoing) => GetByIdAsync<EkaerHistory?>(SignalRTags.GenerateEkaerXmlDocument, [foreignKey, isOutgoing]);
|
public Task<EkaerHistory?> GenerateEkaerXmlDocument(int foreignKey, bool isOutgoing) => GetByIdAsync<EkaerHistory?>(SignalRTags.GenerateEkaerXmlDocument, [foreignKey, isOutgoing]);
|
||||||
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing) => GetByIdAsync<EkaerHistory?>(SignalRTags.CreateEkaerHistory, [foreignKey, isOutgoing]);
|
public Task<EkaerHistory?> CreateEkaerHistory(int foreignKey, bool isOutgoing) => GetByIdAsync<EkaerHistory?>(SignalRTags.CreateEkaerHistory, [foreignKey, isOutgoing]);
|
||||||
public Task<int> CreateMissingEkaerHistories(DateTime fromDate) => GetByIdAsync<int>(SignalRTags.CreateMissingEkaerHistories, fromDate);
|
public Task<EkaerCreateResult?> CreateMissingEkaerHistories(DateTime fromDate) => GetByIdAsync<EkaerCreateResult?>(SignalRTags.CreateMissingEkaerHistories, fromDate);
|
||||||
public Task<int> GetEkaerHistoryCount(EkaerHistoryFilter filter) => GetByIdAsync<int>(SignalRTags.GetEkaerHistoryCount, filter);
|
public Task<int> GetEkaerHistoryCount(EkaerHistoryFilter filter) => GetByIdAsync<int>(SignalRTags.GetEkaerHistoryCount, filter);
|
||||||
#endregion EkaerHistory
|
#endregion EkaerHistory
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@
|
||||||
"UseStatefulReconnect": true
|
"UseStatefulReconnect": true
|
||||||
},
|
},
|
||||||
"AcBinaryHubProtocol": {
|
"AcBinaryHubProtocol": {
|
||||||
"ProtocolMode": "AsyncSegment",
|
"ProtocolMode": "Bytes",
|
||||||
"BufferSize": 4096,
|
"BufferSize": 4096,
|
||||||
"FlushPolicy": "Coalesced",
|
"FlushPolicy": "Coalesced",
|
||||||
"FlushTimeout": "00:00:10"
|
"FlushTimeout": "00:00:15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@
|
||||||
"UseStatefulReconnect": true
|
"UseStatefulReconnect": true
|
||||||
},
|
},
|
||||||
"AcBinaryHubProtocol": {
|
"AcBinaryHubProtocol": {
|
||||||
"ProtocolMode": "AsyncSegment",
|
"ProtocolMode": "Bytes",
|
||||||
"BufferSize": 4096,
|
"BufferSize": 4096,
|
||||||
"FlushPolicy": "Coalesced",
|
"FlushPolicy": "Coalesced",
|
||||||
"FlushTimeout": "00:00:10"
|
"FlushTimeout": "00:00:15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@
|
||||||
"UseStatefulReconnect": true
|
"UseStatefulReconnect": true
|
||||||
},
|
},
|
||||||
"AcBinaryHubProtocol": {
|
"AcBinaryHubProtocol": {
|
||||||
"ProtocolMode": "AsyncSegment",
|
"ProtocolMode": "Bytes",
|
||||||
"BufferSize": 4096,
|
"BufferSize": 4096,
|
||||||
"FlushPolicy": "Coalesced",
|
"FlushPolicy": "Coalesced",
|
||||||
"FlushTimeout": "00:00:10"
|
"FlushTimeout": "00:00:15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue