diff --git a/Nop.Plugin.Misc.AIPlugin/Controllers/FruitBankDataController.cs b/Nop.Plugin.Misc.AIPlugin/Controllers/FruitBankDataController.cs index 61171a8..b4bba8d 100644 --- a/Nop.Plugin.Misc.AIPlugin/Controllers/FruitBankDataController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Controllers/FruitBankDataController.cs @@ -363,13 +363,11 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers // A forrás-entitás léte irányfüggő: bejövő → ShippingDocument, kimenő → Order. if (!isOutgoing) { - _ = await ctx.ShippingDocuments.GetByIdAsync(foreignKey, false) - ?? throw new ArgumentException($"ShippingDocument not found; id: {foreignKey}", nameof(foreignKey)); + _ = await ctx.ShippingDocuments.GetByIdAsync(foreignKey, false) ?? throw new ArgumentException($"ShippingDocument not found; id: {foreignKey}", nameof(foreignKey)); } else { - _ = await ctx.OrderDtos.GetByIdAsync(foreignKey, false) - ?? throw new ArgumentException($"Order not found; id: {foreignKey}", nameof(foreignKey)); + _ = await ctx.OrderDtos.GetByIdAsync(foreignKey, false) ?? throw new ArgumentException($"Order not found; id: {foreignKey}", nameof(foreignKey)); } var ekaerHistory = new EkaerHistory { ForeignKey = foreignKey, IsOutgoing = isOutgoing, Status = EkaerStatus.Pending }; @@ -384,12 +382,15 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers /// idempotens, és bármikor újrafuttatható (maga a gomb a "reconciliation"). /// [SignalR(SignalRTags.CreateMissingEkaerHistories)] - public async Task CreateMissingEkaerHistories(DateTime fromDate) + public async Task CreateMissingEkaerHistories(DateTime fromDate) { _logger.Detail($"CreateMissingEkaerHistories invoked; fromDate: {fromDate:yyyy-MM-dd}"); - // Bejövő: rekord nélküli szállítólevelek a dátumtól — Partnerrel és tételekkel betöltve a kapuhoz - // (IsEkaer-mentesség + tömeg/érték küszöb). A fájl-blobokat NEM töltjük (csak Partner + Items). + var toCreate = new List(); + var messages = new HashSet(); // dedup: ugyanaz a partner (pl. hiányzó országkód) több csoportban is előjöhet + + // ── Bejövő ── Rekord nélküli szállítólevelek a dátumtól, Partnerrel + tételekkel betöltve a kötelezettség-kapuhoz. + // A bejelentés-egység (Shipping, Partner) = feladó→címzett→jármű: a küszöb a csoport ÖSSZES dokumentumára aggregál. var candidates = await ctx.ShippingDocuments.GetAll() .Where(sd => sd.ShippingDate >= fromDate) .Where(sd => !ctx.EkaerHistories.Table.Any(eh => eh.ForeignKey == sd.Id && !eh.IsOutgoing)) @@ -397,33 +398,58 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers .LoadWith(sd => sd.ShippingItems) .ToListAsync(); - var missingInbound = new List(); - foreach (var doc in candidates) + // Partner nélküli (még nincs rendesen felvett) szállítólevél CSENDBEN kihagyva — nincs feladó, nem kész; + // a mentes partner (IsEkaer=false) is. A maradék (Shipping, Partner) csoportban (a dokumentum mindig kap Shippinget). + foreach (var group in candidates.Where(sd => sd.Partner != null && sd.Partner.IsEkaer != false).GroupBy(sd => (sd.ShippingId, sd.PartnerId))) { - // Explicit mentesítés (pl. nagybani piac, azonos cím — nincs közúti fuvar a partnerek között). - if (doc.Partner?.IsEkaer == false) continue; + var docs = group.ToList(); + try + { + var result = fruitBankEkaerService.EvaluateObligation(docs); + if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; } + if (result.Obligation == EkaerObligation.NotRequired) continue; - var items = doc.ShippingItems ?? []; - var totalWeight = items.Sum(i => i.MeasuredGrossWeight); - var rateToHuf = EkaerValueCalculator.ResolveRateToHuf(doc.Partner?.Currency, ekaerSettings.EurHufRate); - var totalValueHuf = items.Sum(EkaerValueCalculator.ItemLineValue) * rateToHuf; - - // Küszöb alatt (tömeg ÉS érték is) → nem kötelező EKÁER → nem hozunk létre sort. - if (totalWeight < ekaerSettings.ThresholdWeightKg && totalValueHuf < ekaerSettings.ThresholdValueHuf) continue; - - missingInbound.Add(new EkaerHistory { ForeignKey = doc.Id, IsOutgoing = false, StatusId = (int)EkaerStatus.Pending }); + // Kötelező → a csoport minden dokumentumára egy-egy Pending sor (a rekord-szerkezet per-dokumentum marad). + foreach (var doc in docs) + toCreate.Add(new EkaerHistory { ForeignKey = doc.Id, IsOutgoing = false, StatusId = (int)EkaerStatus.Pending }); + } + catch (Exception ex) + { + _logger.Error($"CreateMissingEkaerHistories; inbound evaluate failed; ShippingId: {group.Key.ShippingId}; PartnerId: {group.Key.PartnerId}", ex); + messages.Add($"Szállítólevél-csoport (Shipping #{group.Key.ShippingId}, Partner #{group.Key.PartnerId}): a kötelezettség nem dönthető el — {ex.Message}"); + } } - // Kimenő: rekord nélküli, lezárt (Complete) rendelések, DateOfReceipt a dátumtól (jövőbeli is — előre-bejelentés). - var missingOutgoing = await ctx.OrderDtos.GetAllByOrderStatus(OrderStatus.Complete, false) + // ── Kimenő ── Rekord nélküli, lezárt (Complete) rendelések a dátumtól; rendelésenként vizsgáljuk (nincs Shipping). + // Előbb csak az azonosítók (könnyű szűrés), majd EGY batch-betöltés a teljes gráffal (GetAllByIds) — nincs N+1. + var missingOrderIds = await ctx.OrderDtos.GetAllByOrderStatus(OrderStatus.Complete, false) .Where(o => o.GenericAttributes.Any(ga => ga.Key == nameof(OrderDto.DateOfReceipt) && DateTime.Parse(ga.Value) >= fromDate.Date)) .Where(o => !ctx.EkaerHistories.Table.Any(eh => eh.ForeignKey == o.Id && eh.IsOutgoing)) - .Select(o => new EkaerHistory { ForeignKey = o.Id, IsOutgoing = true, StatusId = (int)EkaerStatus.Pending }) + .Select(o => o.Id) .ToListAsync(); - var createdCount = 0; + var missingOrders = missingOrderIds.Count == 0 ? [] : await ctx.OrderDtos.GetAllByIds(missingOrderIds, true).ToListAsync(); - foreach (var ekaerHistory in missingInbound.Concat(missingOutgoing)) + foreach (var order in missingOrders) + { + try + { + var result = fruitBankEkaerService.EvaluateObligation(order); + if (result.Obligation == EkaerObligation.DataError) { messages.UnionWith(result.Errors); continue; } + if (result.Obligation == EkaerObligation.NotRequired) continue; + + toCreate.Add(new EkaerHistory { ForeignKey = order.Id, IsOutgoing = true, StatusId = (int)EkaerStatus.Pending }); + } + catch (Exception ex) + { + _logger.Error($"CreateMissingEkaerHistories; outbound evaluate failed; OrderId: {order.Id}", ex); + messages.Add($"Rendelés #{order.Id}: a kötelezettség nem dönthető el — {ex.Message}"); + } + } + + // ── Beszúrás ── + var createdCount = 0; + foreach (var ekaerHistory in toCreate) { try { @@ -436,8 +462,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers } } - _logger.Info($"CreateMissingEkaerHistories; created: {createdCount} (inbound: {missingInbound.Count}, outgoing: {missingOutgoing.Count}); fromDate: {fromDate:yyyy-MM-dd}"); - return createdCount; + _logger.Info($"CreateMissingEkaerHistories; created: {createdCount}; messages: {messages.Count}; fromDate: {fromDate:yyyy-MM-dd}"); + return new EkaerCreateResult { CreatedCount = createdCount, Messages = [.. messages.OrderBy(m => m)] }; } [SignalR(SignalRTags.GetShippings)] diff --git a/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs b/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs index 6f6059d..967692d 100644 --- a/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs +++ b/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs @@ -259,8 +259,10 @@ public class PluginNopStartup : INopStartup }) .AddAcBinaryProtocol(opts => { - opts.ProtocolMode = BinaryProtocolMode.AsyncSegment; + //opts.ProtocolMode = BinaryProtocolMode.AsyncSegment; + opts.ProtocolMode = BinaryProtocolMode.Bytes; opts.FlushPolicy = FlushPolicy.Coalesced; + opts.FlushTimeout = TimeSpan.FromSeconds(15); // Explicit AcLogger instance (name-based category, matches the previous setup). // If omitted, the options extension falls back to ILogger from DI. diff --git a/Nop.Plugin.Misc.AIPlugin/docs/EKAER/EKAER_TODO.md b/Nop.Plugin.Misc.AIPlugin/docs/EKAER/EKAER_TODO.md index 67127b0..4ef6ecc 100644 --- a/Nop.Plugin.Misc.AIPlugin/docs/EKAER/EKAER_TODO.md +++ b/Nop.Plugin.Misc.AIPlugin/docs/EKAER/EKAER_TODO.md @@ -15,13 +15,13 @@ A `ShippingToEkaerMapper` megírása előtt tisztázandó pontok. Minden sor a j | # | Téma | Jelenlegi feltételezés | Nyitott kérdés | |---|------|------------------------|----------------| -| 1 | `tradeType` (E/I/D irány) | Bejövő = beszerzés. HU feladó → **D** (belföldi), egyébként → **I** (import). A feladó országát a `ShippingDocument.Partner` / `Country` adja. | Automatikusan az országból dőljön el, vagy kézzel (UI) állítható? A kimenő (**E**, export) egyelőre hatókörön kívül (lásd #7). | +| 1 | `tradeType` (E/I/D irány) | ✅ **Frissítve (2026-06):** a feladó és a címzett **országkódjának összevetése** dönt: egyezik → **D** (belföld); eltér → kimenőnél **E** (export), bejövőnél **I** (import). (Korábban: HU ↔ nem-HU.) | A `Customer.CountryId` → ISO-feloldás kimenőnél még TODO (#7) → addig a vevő HU, ezért a kimenő ténylegesen D marad. | | 2 | Célhely / telephely | ✅ **Megoldva:** configból (`Ekaer:Company` + `Site` al-szekció) → `IEkaerSettings.Company` + `EkaerCompanyInfo.Site` (`LocationType`, fel-/lerakodás). Nincs hardcode. | Több telephely (mint a `PartnerDepot`) — későbbi, ha kell. | | 3 | Érték (`value`, HUF) | ✅ **Megoldva:** `UnitPriceOnDocument × QuantityOnDocument × árfolyam` (`EkaerValueCalculator`). Pénznem a `Partner.Currency`-ből; EUR-HUF árfolyam configból (`Ekaer:ExchangeRate:EurHuf`, MNB középárfolyam). | Árfolyam dinamizálása (jelenleg „drót" config-érték) — későbbi. | | 4 | Tömeg (`weight`) | `ShippingItem.MeasuredGrossWeight` (bruttó). | Az EKÁER **bruttó** tömeget vár — megerősíteni, hogy ez bruttó és nem nettó. | | 5 | Granularitás | 1 `ShippingDocument` → 1 `tradeCard`; 1 `Shipping` → több művelet (több dokumentum). | Megerősíteni: tényleg dokumentumonként egy tradeCard? | | 6 | Eladó (`seller*`) mezők | `ShippingDocument.Partner`-ből: `Name`, `TaxId` (első 8 jegy = adószám-törzs), `CountryCode`, `City`, `Street`, `PostalCode`. | A `Partner` (`PartnerBase`) pontos mezőnevei/forrásai — megerősíteni. | -| 7 | Kimenő irány (`Order` → EKÁER) | ✅ **Implementálva** (`MapOrder`): belföldi értékesítés (**D**/**S**), mi=eladó, vevő=címzett; tömeg `OrderItemDto.GrossWeight`, érték nettó (UnitPriceExclTax × qty), jelenleg minden HUF (rate 1). | **Nyitott:** a vonó jármű / fuvarozó forrása — az Orderen NINCS; az OrderDto-ba bekötendő (a `MapOrder` `Vehicle`/`CarrierText` TODO-ja). Export (**E**) + külföldi deviza + a `Customer.CountryId` ISO-feloldása: későbbi. | +| 7 | Kimenő irány (`Order` → EKÁER) | ✅ **Implementálva** (`ToConsignment(OrderDto)`): mi=eladó, vevő=címzett; tömeg `OrderItemDto.GrossWeight`, érték nettó (UnitPriceExclTax × qty), jelenleg minden HUF (rate 1). **Fuvarozó = `Customer.Company`** (a vevő viszi el — 2026-06). | **Nyitott:** a vonó jármű (**rendszám**) forrása — a `Customer`-hez még nincs bekötve (üres → warning, a felrakodás megkezdéséig pótolandó). Külföldi deviza + `Customer.CountryId` ISO-feloldás (export `E`): későbbi. | | 8 | `productVtsz` forrás | `ProductDto.Gtin` (átmeneti). | GTIN ≠ VTSZ — lásd [`MGFBANKPLUG-EKAER-I-T3X8`](EKAER_ISSUES.md). Hosszú távon külön `Vtsz` mező. | | 9 | tétel `tradeReason` | `A` (beszerzés) — a mapper ezt használja bejövő árura. | Korábban tévesen `S`-nek feltételezve; az enum valójában `S`=értékesítés, `A`=beszerzés. Üzletileg megerősítendő. | | 10 | `value` mező | ✅ **Megoldva:** a value most töltve (lásd #3); 0 / ismeretlen ár esetén `null` marad. | A NAV 2021-től `value > 0`-t vár — a validátorban a `value > 0` üzleti szabály még hozzáadandó. | @@ -44,7 +44,11 @@ Az EKÁER bejelentés-kötelezettség küszöbe **termékfüggő** — a VTSZ ko | Kockázatos egyéb (nem élelmiszer) | 500 kg | 1 000 000 Ft | | Nem kockázatos | 2500 kg | 5 000 000 Ft | -**Jelenlegi (átmeneti):** a `CreateMissingEkaerHistories` egyetlen, globális küszöböt használ az `EkaerSettings`-ből (config: 200 kg / 250 000 Ft — a legszigorúbb, hogy ne legyen aluljelentés). +> **A fenti értékek a 13/2020. (XII. 23.) PM rendelet kategóriái — REFERENCIA, 2026-06 állapot.** Az alkalmazott küszöb a kódba **NINCS drótozva** (`appsettings` → `Ekaer:Thresholds`), és a jogszabály/beállítás változásával módosul; ezt a táblát csak tájékoztatásul, **dátumozva** tartjuk. +> +> ⚠️ **A túljelentés is bírság** (megerősítve, 2026-06): a globális „legszigorúbb" küszöb egy **nem-élelmiszer** terméket (ami csak a magasabb határnál kötelező) **túljelentene**. Fruit-importőrnél (minden áru kockázatos élelmiszer) ez nem gond — de amint nem-élelmiszer is bekerül, ez a per-termék kategória **szükségessé** válik (nem csak P2). + +**Jelenlegi (átmeneti):** a `CreateMissingEkaerHistories` egyetlen, globális küszöböt használ az `EkaerSettings`-ből — **configból** (`Ekaer:Thresholds`), a kódba **NEM drótozva**. Jelenleg a **kockázatos-élelmiszer** határ van beállítva (a fruit-importőr profil miatt, a legszigorúbb a beszerzéseknél). A kapu a küszöböt `(Shipping, Partner)`-re **aggregálva** nézi, külföldinél küszöb nélkül kötelez (lásd README → kapu-szakasz). **Cél:** a **terméknél (Product) kézzel megadható** küszöb-**KATEGÓRIA** (nem a konkrét érték!) — egy **enum** (pl. `EkaerThresholdCategory { RiskyFood, RiskyOther, NonRisky }`) —, **DB-ben tárolva** (Product oszlop / GenericAttribute). A konkrét kg/Ft értékeket a **kód** rendeli a kategóriához → értékhatár-változáskor egy helyen módosul. A küszöb-kapu dokumentumonként a tételek kategóriái szerint alkalmazza a megfelelő határt. (Későbbi lépés: a kategória automatikus levezetése a NAV kockázatos-VTSZ-listákból.) diff --git a/Nop.Plugin.Misc.AIPlugin/docs/EKAER/README.md b/Nop.Plugin.Misc.AIPlugin/docs/EKAER/README.md index 4f8b81b..ec67695 100644 --- a/Nop.Plugin.Misc.AIPlugin/docs/EKAER/README.md +++ b/Nop.Plugin.Misc.AIPlugin/docs/EKAER/README.md @@ -11,7 +11,7 @@ A FruitBank EKÁER-bejelentés **szerver-oldali** (a kliens már standalone WASM | Réteg | Hol | Mit ad | |---|---|---| | **NAV/EKÁER framework** | `AyCode.Services/Nav/` + `Nav/Ekaer/` (AyCode.Core) | transport, auth (SHA-512), generált modellek, **`EkaerTradeCardValidator`** (NAV-séma + üzleti szabályok), **`EkaerSubmitService`** (validate→send), `EkaerManageService` (HTTP). Általános — **nem** FruitBank-specifikus. | -| **FruitBank leképezés** | `FruitBank.Common/Services/Ekaer/` | **`ShippingToEkaerMapper`** — `Shipping` → `tradeCard` ops (tiszta leképező, nem validál) | +| **FruitBank leképezés** | `FruitBank.Common/Services/Ekaer/` | **`ShippingToEkaerMapper`** — a forrást (bejövő `ShippingDocument`-csoport / kimenő `OrderDto`) egy irány-független **`EkaerConsignment`**-re képezi (`ToConsignment`), abból épül a `tradeCard` (`BuildTradeCard`). **`EkaerReportability`** — bejelentés-kötelezettség (küszöb/külföld). **`EkaerValueCalculator`** — tétel-érték HUF-ban. | | **FruitBank fogyasztó** | `FruitBank.Common.Server/Services/Ekaer/` | **`FruitBankEkaerService`** — szerver-oldali orchestráció: credentials/options + map → submit | | **nopCommerce-integráció (ez a plugin)** | ez a folder + a plugin kód | DI-bekötés, NAV-fiók settings, a VTSZ-forrás (`Product.Gtin`), SignalR-trigger a kliens felől, és ez a doksi | @@ -26,6 +26,10 @@ FruitBankEkaerService.SubmitShippingAsync(shipping) // FruitBank.Common.S ``` Validációs hiba → **hibalista** (`EkaerSubmitResult.Invalid`), nem megy ki kérés. NAV-oldali hiba → `NavReportException` propagál. +**További belépési pontok** (a kézi NAV-beadás munkafolyamathoz — `FruitBankDataController` SignalR-endpointok, kliens felől triggerelve): +- **`CreateMissingEkaerHistories(fromDate)`** — reconciliation-kapu: a dátumtól rekord nélküli szállítólevelekre/rendelésekre `Pending` `EkaerHistory` sorokat hoz létre, **csak ha kötelező** (lásd a kapu-szakaszt lentebb). `EkaerCreateResult`-ot ad vissza (létrehozott szám + a kihagyott tételek üzenetei). +- **`GenerateEkaerXmlDocument(foreignKey, isOutgoing)`** — egy sorra legenerálja + validálja a tradeCard XML-t (NEM küld NAV-ot); a `Status` / `ErrorText` / `ConversionRate` töltődik, az XML vágólapra másolható a kézi beadáshoz. + ## Companion fájlok - [`EKAER_ISSUES.md`](EKAER_ISSUES.md) — ismert problémák (pl. `MGFBANKPLUG-EKAER-I-T3X8`: GTIN≠VTSZ). @@ -36,7 +40,7 @@ Validációs hiba → **hibalista** (`EkaerSubmitResult.Invalid`), nem megy ki k | EKÁER | FruitBank forrás | |---|---| | `seller*` (feladó) | `ShippingDocument.Partner` (a beszállító) | -| `carrierText` (fuvarozó, szöveges) | `Shipping.CargoPartner.Name` | +| `carrierText` (fuvarozó, szöveges) | **bejövő:** `Shipping.CargoPartner.Name`; **kimenő:** `Customer.Company` (a vevő viszi el az árut) | | `vehicle` / `vehicle2` | `Shipping.CargoTruck` / `CargoTrailer` (`LicencePlate` + `CountryCode`, normalizálva) | | tétel `productVtsz` | `ShippingItem.ProductDto.Gtin` (átmenetileg — lásd `EKAER_ISSUES.md`) | | tétel `productName` | `ShippingItem.ProductName` | @@ -45,6 +49,23 @@ Validációs hiba → **hibalista** (`EkaerSubmitResult.Invalid`), nem megy ki k A nyitott pontok (tradeType, destination, value/deviza, granularitás, kimenő irány): [`EKAER_TODO.md`](EKAER_TODO.md). +## Reconciliation-kapu, kötelezettség, életciklus (2026-06) + +A kézi NAV-beadás munkafolyamat: a **kapu** létrehozza a sorokat → a user **generál** + másol/beküld → kézzel rögzíti a NAV-választ. + +**`EkaerHistory` életciklus** (egy sor = egy tradeCard; `ForeignKey` = bejövő `ShippingDocument.Id` / kimenő `Order.Id` + `IsOutgoing`; a `Status` int-oszlop + `[NotColumn]` enum-nézet): +`Pending` (a kapu hozta létre) → **Generate** → `Generated` / `GeneratedWithWarning` (küldhető, de pótlandó — pl. hiányzó rendszám) / `ValidationError` (blokkoló) → kézi NAV-beadás után `Sent` / `SentWithMissingData` (pótlásra vár). A grid 3 tabja ezekre szűr (szerver-oldali `EkaerHistoryFilter`), a warning ⚠️ / error ⛔ ikonnal megkülönböztetve. + +**Bejelentés-kötelezettség** — `EkaerReportability.Evaluate(consignment, settings)`, a kapu hívja egységenként: +- **egység:** bejövőnél a `(Shipping, Partner)` csoport (= feladó→címzett→jármű) — a tömeg/érték a csoport **ÖSSZES** dokumentumára **aggregálva** (13/2020 PM rendelet); kimenőnél egy rendelés; +- **küszöb felett** → kötelező, az országkódtól **függetlenül** (az országkód-hibát a generate-validálás jelzi); +- **küszöb alatt** → csak a **külföldi** reláció kötelező (a feladó és a címzett országkódja **eltér**); belföld → nem; hiányzó/érvénytelen (nem ISO-2) országkód → **`DataError`** (a sor kimarad + üzenet a popupban); +- **mentesség:** `Partner.IsEkaer = false`, és a Partner nélküli (félkész) szállítólevél — csendben kimaradnak. + +> **A küszöb configból jön** (`Ekaer:Thresholds:WeightKg` / `:ValueHuf`) — a kódba **NINCS drótozva**, mert a NAV-jogszabály és a beállítás szerint **változhat**. A konkrét, dátumozott értékeket és a termékkategóriákat lásd [`EKAER_TODO.md`](EKAER_TODO.md) **T-W3R8**. A NAV **mindkét irányban büntet** (alul- ÉS túljelentés), ezért a küszöbnek pontosnak kell lennie. + +**Egyesített leképezés:** a bejövő/kimenő forrás egy **`EkaerConsignment`** köztes modellre képződik (két adapter), és **abból** épül MIND a tradeCard (`BuildTradeCard`), MIND a kötelezettség-döntés — így az irányfüggő rész két adapterre szorul, a közös logika egy helyen van. A `MapDocument` / `MapOrder` / `MapShipping` ennek vékony wrappere. + ## Bekötés a szerveren (DI) A plugin (vagy a szerver-startup) köti be a láncot — a NAV-fiók titkos adatai a szerver config/secret store-jából: @@ -54,6 +75,6 @@ services.AddHttpClient(/* NAV TLS 1.2 */); services.AddScoped(); services.AddScoped(); services.AddScoped(); -services.AddSingleton(/* EkaerMappingOptions: destination + saját raktár */); +services.AddSingleton(/* EkaerSettings: Ekaer:Company + Site + küszöbök + EUR-HUF árfolyam — configból */); services.AddScoped(); ```