diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 2ef6a2f4..9fdc5ea2 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -42,7 +42,17 @@
"Bash(awk '{print toupper\\($1\\)}')",
"Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" status --short)",
"Bash(git -C \"H:/Applications/Mango/Source/FruitBankHybridApp\" status --short)",
- "Bash(git -C \"H:/Applications/Mango/Source/NopCommerce.Common\" status --short)"
+ "Bash(git -C \"H:/Applications/Mango/Source/NopCommerce.Common\" status --short)",
+ "Bash(Get-ChildItem -Path \"H:\\\\Applications\\\\Mango\\\\Source\\\\NopCommerce.Common\\\\4.70\\\\Plugins\\\\Nop.Plugin.Misc.AIPlugin\" -Directory)",
+ "Bash(Select-Object Name)",
+ "Bash(ICargo)",
+ "Bash(IShipping\")",
+ "Bash(ls -la \"H:\\\\Applications\\\\Mango\\\\Source\\\\NopCommerce.Common\\\\4.70\\\\Plugins\\\\Nop.Plugin.Misc.AIPlugin\" 2>/dev/null | head -30)",
+ "PowerShell(ls \"H:\\\\Applications\\\\Mango\\\\Source\\\\FruitBankHybridApp\\\\FruitBank.Common\\\\Interfaces\\\\\" | where {$_.Name -match 'IPartner|ICargo|IShipping'} | select Name)",
+ "Bash(find \"H:\\\\Applications\\\\FruitBankHybridApp\\\\FruitBank.Common\\\\Source\" -type f -name \"*Shipping*.cs\" 2>/dev/null | head -10)",
+ "Bash(find \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Services\" -type d | head -20)",
+ "Bash(rm -f H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Services/Ekaer/EkaerMappingOptions.cs H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Services/Ekaer/IShippingToEkaerMapper.cs H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Services/Ekaer/ShippingToEkaerMapper.cs)",
+ "Bash(rmdir \"H:/Applications/Mango/Source/NopCommerce.Common/4.70/Plugins/Nop.Plugin.Misc.AIPlugin/Services/Ekaer\")"
]
}
}
diff --git a/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs b/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs
new file mode 100644
index 00000000..2e457d1a
--- /dev/null
+++ b/FruitBank.Common.Server/Services/Ekaer/FruitBankEkaerService.cs
@@ -0,0 +1,35 @@
+using AyCode.Services.Nav.Ekaer;
+using AyCode.Services.Nav.Ekaer.Models;
+using FruitBank.Common.Entities;
+using FruitBank.Common.Services.Ekaer;
+
+namespace FruitBank.Common.Server.Services.Ekaer;
+
+///
+///
+/// A teljes lánc: map (, FruitBank.Common) →
+/// validate → send (, AyCode.Services). A NAV-fiók
+/// hitelesítő adatait (INavCredentials) és a -t a DI szolgáltatja.
+///
+public sealed class FruitBankEkaerService : IFruitBankEkaerService
+{
+ private readonly IShippingToEkaerMapper _mapper;
+ private readonly IEkaerSubmitService _submitService;
+ private readonly EkaerMappingOptions _options;
+
+ public FruitBankEkaerService(IShippingToEkaerMapper mapper, IEkaerSubmitService submitService, EkaerMappingOptions options)
+ {
+ _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ _submitService = submitService ?? throw new ArgumentNullException(nameof(submitService));
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ }
+
+ public Task 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, _options, operation);
+ return _submitService.SubmitAsync(operations, cancellationToken);
+ }
+}
diff --git a/FruitBank.Common.Server/Services/Ekaer/IFruitBankEkaerService.cs b/FruitBank.Common.Server/Services/Ekaer/IFruitBankEkaerService.cs
new file mode 100644
index 00000000..d9f13b9c
--- /dev/null
+++ b/FruitBank.Common.Server/Services/Ekaer/IFruitBankEkaerService.cs
@@ -0,0 +1,19 @@
+using AyCode.Services.Nav.Ekaer;
+using AyCode.Services.Nav.Ekaer.Models;
+using FruitBank.Common.Entities;
+
+namespace FruitBank.Common.Server.Services.Ekaer;
+
+///
+/// A FruitBank szerver-oldali EKÁER-fogyasztója: egy -et leképez EKÁER tradeCard-okra
+/// (a mapperrel), majd beküldi (az általános submit-orchestrátorral). Ez a vékony, projekt-specifikus réteg;
+/// az általános NAV/EKÁER logika (validátor, submit, manage) az AyCode.Services-ben él.
+///
+public interface IFruitBankEkaerService
+{
+ ///
+ /// Leképezi és beküldi a szállítmányt. Az eredmény vagy validációs hibák (nem ment ki kérés),
+ /// vagy a NAV-válasz — lásd .
+ ///
+ Task SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default);
+}
diff --git a/FruitBank.Common/Services/Ekaer/EkaerMappingOptions.cs b/FruitBank.Common/Services/Ekaer/EkaerMappingOptions.cs
new file mode 100644
index 00000000..88122619
--- /dev/null
+++ b/FruitBank.Common/Services/Ekaer/EkaerMappingOptions.cs
@@ -0,0 +1,36 @@
+using AyCode.Services.Nav.Ekaer.Models;
+
+namespace FruitBank.Common.Services.Ekaer;
+
+///
+/// A konfiguráció-függő bemenetei, amelyek NEM a Shipping-ből
+/// származnak: a bejelentő (FruitBank) mint címzett/destination cégadatai, a lerakodási hely (saját raktár),
+/// és a saját országkód a tradeType irány meghatározásához.
+///
+/// Ezek később a szerver-oldali plugin beállításaiból (settings) töltődnek — lásd a plugin docs/EKAER/EKAER_TODO.md #2.
+public sealed class EkaerMappingOptions
+{
+ /// A bejelentő (FruitBank) neve — a tradeCard destinationName-je bejövő relációban.
+ public string? DestinationName { get; set; }
+
+ /// A bejelentő adószáma (destinationVatNumber). Pattern: [0-9A-Z-]{1,15}.
+ public string? DestinationVatNumber { get; set; }
+
+ /// A bejelentő országkódja (2 betű). Alapértelmezés: HU.
+ public string DestinationCountryCode { get; set; } = "HU";
+
+ /// A bejelentő címe egybeírva (destinationAddress, max 200).
+ public string? DestinationAddress { get; set; }
+
+ ///
+ /// A lerakodási / raktározási hely (a saját raktár). Magyar cím esetén a Name/VatNumber/Phone/Email
+ /// kitöltése kötelező — ezért kész -ként adjuk át (a plugin-konfigból összeállítva).
+ ///
+ public LocationType? UnloadLocation { get; set; }
+
+ ///
+ /// A bejelentő saját országkódja a tradeType meghatározásához: ha a feladó (beszállító) országa
+ /// ezzel egyezik → D (belföld-belföld), egyébként → I (import). Alapértelmezés: HU.
+ ///
+ public string HomeCountryCode { get; set; } = "HU";
+}
diff --git a/FruitBank.Common/Services/Ekaer/IShippingToEkaerMapper.cs b/FruitBank.Common/Services/Ekaer/IShippingToEkaerMapper.cs
new file mode 100644
index 00000000..47af2cbc
--- /dev/null
+++ b/FruitBank.Common/Services/Ekaer/IShippingToEkaerMapper.cs
@@ -0,0 +1,26 @@
+using AyCode.Services.Nav.Ekaer.Models;
+using FruitBank.Common.Entities;
+
+namespace FruitBank.Common.Services.Ekaer;
+
+///
+/// FruitBank domain → NAV EKÁER tradeCard leképezés. Egy bejövő -ből
+/// EKÁER tradeCard műveleteket állít elő (dokumentumonként egyet).
+///
+///
+/// A NAV-protokollt és az authentikációt NEM kezeli — az az AyCode.Services.Nav réteg
+/// (NavReportServiceBase / EkaerManageService) felelőssége. A tényleges NAV-bejelentést
+/// a szerver-oldali (nopCommerce plugin) service végzi, amely ezt a leképezőt használja. A leképezés
+/// tisztázott pontjai és nyitott döntései a plugin docs/EKAER/README.md és EKAER_TODO.md fájljaiban.
+///
+public interface IShippingToEkaerMapper
+{
+ ///
+ /// Leképezi a minden ShippingDocument-jét egy-egy EKÁER tradeCard műveletre.
+ ///
+ /// A bejövő szállítmány. A fuvarozó/jármű a Shipping szintjén, az eladó/tételek a dokumentum szintjén élnek.
+ /// A konfiguráció-függő adatok (destination cég, lerakodási hely, home country).
+ /// A tradeCard művelet típusa. Alapértelmezés: .
+ /// Dokumentumonként egy , a beérkezés sorrendjében indexelve.
+ IReadOnlyList MapShipping(Shipping shipping, EkaerMappingOptions options, OperationType operation = OperationType.Create);
+}
diff --git a/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs b/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs
new file mode 100644
index 00000000..74a76355
--- /dev/null
+++ b/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs
@@ -0,0 +1,168 @@
+using AyCode.Services.Nav.Ekaer.Models;
+using FruitBank.Common.Entities;
+using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
+using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;
+
+namespace FruitBank.Common.Services.Ekaer;
+
+///
+///
+/// Tiszta (állapotmentes) leképező. A TradeType/TradeReasonType enumokat aliasszal hozzuk be,
+/// hogy elkerüljük a Models.Common.TradeCardType enum és a Models.TradeCardType osztály névütközését.
+///
+public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
+{
+ public IReadOnlyList MapShipping(Shipping shipping, EkaerMappingOptions options, OperationType operation = OperationType.Create)
+ {
+ ArgumentNullException.ThrowIfNull(shipping);
+ ArgumentNullException.ThrowIfNull(options);
+
+ var operations = new List();
+ var index = 0;
+
+ // Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
+ foreach (var document in shipping.ShippingDocuments ?? [])
+ {
+ index++;
+ operations.Add(new TradeCardOperationType
+ {
+ Index = index,
+ Operation = operation,
+ TradeCard = BuildTradeCard(shipping, document, options),
+ });
+ }
+
+ return operations;
+ }
+
+ private static TradeCardType BuildTradeCard(Shipping shipping, ShippingDocument document, EkaerMappingOptions options)
+ {
+ var seller = document.Partner; // a beszállító (feladó)
+
+ var tradeCard = new TradeCardType
+ {
+ TradeType = ResolveTradeType(seller, options),
+ ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthatja a bejelentést
+
+ // Feladó / eladó = a beszállító (ShippingDocument.Partner)
+ SellerName = seller?.Name,
+ SellerVatNumber = NormalizeVatNumber(seller?.TaxId),
+ SellerCountry = NormalizeCountryCode(seller?.CountryCode, 2),
+ SellerAddress = BuildAddress(seller),
+
+ // Címzett = a bejelentő (FruitBank) — bejövő relációban (lásd EKAER_TODO #1)
+ DestinationName = options.DestinationName,
+ DestinationVatNumber = NormalizeVatNumber(options.DestinationVatNumber),
+ DestinationCountry = NormalizeCountryCode(options.DestinationCountryCode, 2),
+ DestinationAddress = options.DestinationAddress,
+
+ // Fuvarozó (Shipping.CargoPartner). Regisztrált EKAER-azonosító (Carrier) nincs, csak szöveges név.
+ CarrierText = shipping.CargoPartner?.Name,
+
+ // Lerakodási hely = saját raktár (konfigból); felrakodás = a beszállító telephelye.
+ UnloadLocation = options.UnloadLocation,
+ LoadLocation = BuildLoadLocation(seller),
+ };
+
+ // Vonó jármű + vontatmány: az EKÁER külön bejegyzésként kéri (vehicle / vehicle2), saját rendszámmal/országgal.
+ if (shipping.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck);
+ if (shipping.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer);
+
+ foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item));
+ return tradeCard;
+ }
+
+ ///
+ /// Bejövő (beszerzés) reláció: belföldi feladó → D (belföld-belföld), egyébként → I (import,
+ /// közösségből belföldre). Az export (E) jelenleg nincs leképezve (lásd EKAER_TODO #1, #7).
+ ///
+ private static TradeType ResolveTradeType(Partner? seller, EkaerMappingOptions options)
+ => string.Equals(seller?.CountryCode, options.HomeCountryCode, StringComparison.OrdinalIgnoreCase)
+ ? TradeType.D
+ : TradeType.I;
+
+ private static TradeCardItemType BuildItem(ShippingItem item) => new()
+ {
+ ItemExternalId = item.Id.ToString(),
+ // 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.
+ TradeReason = TradeReasonType.A,
+ ProductVtsz = item.ProductDto?.Gtin, // VTSZ — átmenetileg a Gtin oszlopban (MGFBANKPLUG-EKAER-I-T3X8)
+ ProductName = item.ProductName,
+ Weight = (decimal)item.MeasuredGrossWeight, // bruttó tömeg kg-ban (lásd EKAER_TODO #4)
+ // Value (HUF): a deviza/FX tisztázásáig NEM töltjük (a mező opcionális). Lásd EKAER_TODO #3.
+ };
+
+ private static BasicVehicleDetailType BuildVehicle(CargoTruck truck) => new()
+ {
+ PlateNumber = NormalizePlateNumber(truck.LicencePlate),
+ Country = NormalizeCountryCode(truck.CountryCode, 3),
+ };
+
+ ///
+ /// Felrakodási hely a beszállító címéből. (Magyar feladónál a NAV kötelezővé teszi a Phone/Email-t,
+ /// ami a Partner entitásban nincs — lásd EKAER_TODO #6.)
+ ///
+ private static LocationType? BuildLoadLocation(Partner? seller)
+ {
+ if (seller == null) return null;
+
+ return new LocationType
+ {
+ Name = seller.Name,
+ VatNumber = NormalizeVatNumber(seller.TaxId),
+ Country = NormalizeCountryCode(seller.CountryCode, 2),
+ ZipCode = NormalizeZipCode(seller.PostalCode),
+ City = seller.City,
+ Street = seller.Street,
+ };
+ }
+
+ /// Egybeírt cím (sellerAddress/destinationAddress, max 200): "1234 Budapest Fő utca 1.".
+ private static string? BuildAddress(PartnerBase? partner)
+ {
+ if (partner == null) return null;
+
+ var parts = new[] { partner.PostalCode, partner.City, partner.Street }.Where(p => !string.IsNullOrWhiteSpace(p));
+ var address = string.Join(" ", parts).Trim(); return Truncate(string.IsNullOrWhiteSpace(address) ? null : address, 200);
+ }
+
+ /// Adószám normalizálása. Pattern: [0-9A-Z-]{1,15}.
+ private static string? NormalizeVatNumber(string? value)
+ {
+ if (string.IsNullOrWhiteSpace(value)) return null;
+
+ var cleaned = new string(value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c == '-').ToArray());
+ return Truncate(EmptyToNull(cleaned), 15);
+ }
+
+ /// Országkód normalizálása. Pattern: [A-Z]{1,maxLen} (seller/location: 2, jármű: 3).
+ private static string? NormalizeCountryCode(string? value, int maxLen)
+ {
+ if (string.IsNullOrWhiteSpace(value)) return null;
+
+ var cleaned = new string([.. value.ToUpperInvariant().Where(char.IsAsciiLetter)]);
+ return Truncate(EmptyToNull(cleaned), maxLen);
+ }
+
+ /// Rendszám normalizálása. Pattern: [A-Z0-9ÖŐÜŰ]{4,15} — a kötőjel/szóköz NEM engedett, ezért eltávolítjuk.
+ private static string? NormalizePlateNumber(string? value)
+ {
+ if (string.IsNullOrWhiteSpace(value)) return null;
+
+ var cleaned = new string([.. value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c is 'Ö' or 'Ő' or 'Ü' or 'Ű')]);
+ return Truncate(EmptyToNull(cleaned), 15);
+ }
+
+ /// Irányítószám normalizálása. Pattern: [A-Z0-9 -]{2,10} vagy üres.
+ private static string? NormalizeZipCode(string? value)
+ {
+ if (string.IsNullOrWhiteSpace(value)) return null;
+
+ var cleaned = new string([.. value.ToUpperInvariant().Where(c => char.IsAsciiLetterOrDigit(c) || c is ' ' or '-')]);
+ return Truncate(EmptyToNull(cleaned), 10);
+ }
+
+ private static string? EmptyToNull(string? value) => string.IsNullOrEmpty(value) ? null : value;
+
+ private static string? Truncate(string? value, int maxLen) => value == null ? null : value.Length <= maxLen ? value : value[..maxLen];
+}
diff --git a/FruitBankHybrid.Shared.Tests/Ekaer/ShippingToEkaerMapperTests.cs b/FruitBankHybrid.Shared.Tests/Ekaer/ShippingToEkaerMapperTests.cs
new file mode 100644
index 00000000..eff81443
--- /dev/null
+++ b/FruitBankHybrid.Shared.Tests/Ekaer/ShippingToEkaerMapperTests.cs
@@ -0,0 +1,212 @@
+using AyCode.Services.Nav.Ekaer.Models;
+using FruitBank.Common.Dtos;
+using FruitBank.Common.Entities;
+using FruitBank.Common.Services.Ekaer;
+using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
+using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;
+
+namespace FruitBankHybrid.Shared.Tests.Ekaer;
+
+///
+/// Unit tesztek a -re — a FruitBank Shipping → NAV EKÁER tradeCard
+/// leképezésre. Tisztán memóriában felépített entitásokon fut (nincs hálózat/DB), determinisztikus.
+///
+[TestClass]
+public sealed class ShippingToEkaerMapperTests
+{
+ private static readonly ShippingToEkaerMapper Mapper = new();
+
+ // ---- Helpers ------------------------------------------------------------
+
+ private static Shipping CreateShipping(string sellerCountry = "HU", string plate = "ABC-123", bool withTrailer = true)
+ {
+ var item = new ShippingItem
+ {
+ Id = 1,
+ Name = "Alma",
+ ProductDto = new ProductDto { Gtin = "08081010", Name = "Alma" },
+ MeasuredGrossWeight = 123.5,
+ MeasuredQuantity = 10,
+ UnitPriceOnDocument = 5.0,
+ };
+
+ var document = new ShippingDocument
+ {
+ 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 = [item],
+ };
+
+ var shipping = new Shipping
+ {
+ CargoPartner = new CargoPartner { Name = "Fuvaros Zrt", CountryCode = "HU" },
+ CargoTruck = new CargoTruck { LicencePlate = plate, CountryCode = "HU", IsTrailer = false },
+ ShippingDocuments = [document],
+ };
+
+ if (withTrailer)
+ shipping.CargoTrailer = new CargoTruck { LicencePlate = "XYZ-789", CountryCode = "HU", IsTrailer = true };
+
+ return shipping;
+ }
+
+ private static EkaerMappingOptions CreateOptions() => new()
+ {
+ DestinationName = "FruitBank Kft",
+ DestinationVatNumber = "98765432-2-41",
+ DestinationCountryCode = "HU",
+ DestinationAddress = "1102 Budapest Raktár utca 5",
+ HomeCountryCode = "HU",
+ UnloadLocation = new LocationType
+ {
+ Name = "FruitBank Raktár",
+ VatNumber = "98765432-2-41",
+ Country = "HU",
+ ZipCode = "1102",
+ City = "Budapest",
+ Street = "Raktár utca",
+ StreetNumber = "5",
+ },
+ };
+
+ // ---- Granularitás / index ----------------------------------------------
+
+ [TestMethod]
+ public void MapShipping_ProducesOneOperationPerDocument_WithSequentialIndex()
+ {
+ var shipping = CreateShipping();
+ shipping.ShippingDocuments!.Add(new ShippingDocument
+ {
+ Country = "HU",
+ Partner = new Partner { Name = "Másik Beszállító", CountryCode = "HU" },
+ ShippingItems = [],
+ });
+
+ var ops = Mapper.MapShipping(shipping, CreateOptions());
+
+ Assert.AreEqual(2, ops.Count, "dokumentumonként egy tradeCard");
+ Assert.AreEqual(1, ops[0].Index);
+ Assert.AreEqual(2, ops[1].Index);
+ Assert.AreEqual(OperationType.Create, ops[0].Operation, "alapértelmezett művelet: Create");
+ }
+
+ [TestMethod]
+ public void MapShipping_NullDocuments_ReturnsEmpty()
+ {
+ var ops = Mapper.MapShipping(new Shipping { ShippingDocuments = null }, CreateOptions());
+ Assert.AreEqual(0, ops.Count);
+ }
+
+ [TestMethod]
+ public void MapShipping_HonorsExplicitOperation()
+ {
+ var ops = Mapper.MapShipping(CreateShipping(), CreateOptions(), OperationType.Modify);
+ Assert.AreEqual(OperationType.Modify, ops[0].Operation);
+ }
+
+ // ---- tradeType irány ----------------------------------------------------
+
+ [TestMethod]
+ public void MapShipping_DomesticSeller_TradeTypeDomestic()
+ {
+ var ops = Mapper.MapShipping(CreateShipping(sellerCountry: "HU"), CreateOptions());
+ Assert.AreEqual(TradeType.D, ops[0].TradeCard.TradeType, "belföldi feladó → D");
+ }
+
+ [TestMethod]
+ public void MapShipping_ForeignSeller_TradeTypeImport()
+ {
+ var ops = Mapper.MapShipping(CreateShipping(sellerCountry: "DE"), CreateOptions());
+ Assert.AreEqual(TradeType.I, ops[0].TradeCard.TradeType, "külföldi feladó → I (import)");
+ }
+
+ // ---- Tétel-leképezés ----------------------------------------------------
+
+ [TestMethod]
+ public void MapShipping_MapsItemFields()
+ {
+ var item = Mapper.MapShipping(CreateShipping(), CreateOptions())[0].TradeCard.Items[0];
+
+ Assert.AreEqual("08081010", item.ProductVtsz, "productVtsz = ProductDto.Gtin");
+ Assert.AreEqual("Alma", item.ProductName);
+ Assert.AreEqual(123.5m, item.Weight, "weight = MeasuredGrossWeight (bruttó)");
+ Assert.AreEqual(TradeReasonType.A, item.TradeReason, "bejövő áru = beszerzés = A");
+ Assert.IsNull(item.Value, "a HUF érték a deviza tisztázásáig nincs kitöltve");
+ }
+
+ // ---- Eladó (seller*) ----------------------------------------------------
+
+ [TestMethod]
+ public void MapShipping_MapsSellerFromPartner()
+ {
+ var tradeCard = Mapper.MapShipping(CreateShipping(), CreateOptions())[0].TradeCard;
+
+ Assert.AreEqual("Beszállító Kft", tradeCard.SellerName);
+ Assert.AreEqual("12345678-2-42", tradeCard.SellerVatNumber);
+ Assert.AreEqual("HU", tradeCard.SellerCountry);
+ StringAssert.Contains(tradeCard.SellerAddress, "Budapest");
+ }
+
+ [TestMethod]
+ public void MapShipping_MapsDestinationAndUnloadFromOptions()
+ {
+ var options = CreateOptions();
+ var tradeCard = Mapper.MapShipping(CreateShipping(), options)[0].TradeCard;
+
+ Assert.AreEqual("FruitBank Kft", tradeCard.DestinationName);
+ Assert.AreEqual("HU", tradeCard.DestinationCountry);
+ Assert.AreSame(options.UnloadLocation, tradeCard.UnloadLocation, "a lerakodási hely a konfigból jön");
+ }
+
+ [TestMethod]
+ public void MapShipping_MapsCarrierTextFromCargoPartner()
+ {
+ var tradeCard = Mapper.MapShipping(CreateShipping(), CreateOptions())[0].TradeCard;
+ Assert.AreEqual("Fuvaros Zrt", tradeCard.CarrierText);
+ }
+
+ // ---- Járművek + rendszám-normalizálás ----------------------------------
+
+ [TestMethod]
+ public void MapShipping_NormalizesLicencePlate_RemovesHyphenAndUppercases()
+ {
+ var tradeCard = Mapper.MapShipping(CreateShipping(plate: "abc-123"), CreateOptions())[0].TradeCard;
+ Assert.AreEqual("ABC123", tradeCard.Vehicle!.PlateNumber, "a NAV pattern nem enged kötőjelet, és nagybetűs");
+ Assert.AreEqual("HU", tradeCard.Vehicle.Country);
+ }
+
+ [TestMethod]
+ public void MapShipping_WithTrailer_MapsVehicle2()
+ {
+ var tradeCard = Mapper.MapShipping(CreateShipping(withTrailer: true), CreateOptions())[0].TradeCard;
+ Assert.IsNotNull(tradeCard.Vehicle, "vonó jármű");
+ Assert.IsNotNull(tradeCard.Vehicle2, "vontatmány");
+ Assert.AreEqual("XYZ789", tradeCard.Vehicle2!.PlateNumber);
+ }
+
+ [TestMethod]
+ public void MapShipping_NoTrailer_Vehicle2Null()
+ {
+ var tradeCard = Mapper.MapShipping(CreateShipping(withTrailer: false), CreateOptions())[0].TradeCard;
+ Assert.IsNotNull(tradeCard.Vehicle);
+ Assert.IsNull(tradeCard.Vehicle2, "nincs pótkocsi → nincs vehicle2");
+ }
+
+ // ---- Védőkorlátok -------------------------------------------------------
+
+ [TestMethod]
+ public void MapShipping_NullShipping_Throws()
+ => Assert.ThrowsExactly(() => Mapper.MapShipping(null!, CreateOptions()));
+
+ [TestMethod]
+ public void MapShipping_NullOptions_Throws()
+ => Assert.ThrowsExactly(() => Mapper.MapShipping(CreateShipping(), null!));
+}
diff --git a/FruitBankHybrid.Shared.Tests/OrderClientTests.cs b/FruitBankHybrid.Shared.Tests/OrderClientTests.cs
index cedb2b34..a711f963 100644
--- a/FruitBankHybrid.Shared.Tests/OrderClientTests.cs
+++ b/FruitBankHybrid.Shared.Tests/OrderClientTests.cs
@@ -38,10 +38,10 @@ public sealed class OrderClientTests
var stockTakings = await _signalRClient.GetStockTakings(true);
Assert.IsNotNull(stockTakings);
- Assert.IsTrue(stockTakings.Count != 0);
+ Assert.IsNotEmpty(stockTakings);
Assert.IsTrue(stockTakings.All(o => o.StockTakingItems != null));
- Assert.IsTrue(stockTakings.All(o => o.StockTakingItems.All(oi => oi.Product != null && oi.Product.Id == oi.ProductId)));
+ Assert.IsTrue(stockTakings.All(o => o.StockTakingItems!.All(oi => oi.Product != null && oi.Product.Id == oi.ProductId)));
}
[TestMethod]
@@ -50,9 +50,9 @@ public sealed class OrderClientTests
var stockTakingItems = await _signalRClient.GetStockTakingItems();
Assert.IsNotNull(stockTakingItems);
- Assert.IsTrue(stockTakingItems.Count != 0);
+ Assert.IsNotEmpty(stockTakingItems);
- Assert.IsTrue(stockTakingItems.All(oi => oi.StockTaking != null && oi.Product != null && oi.Product.Id == oi.ProductId));
+ Assert.IsTrue(stockTakingItems.All(oi => oi is { StockTaking: not null, Product: not null } && oi.Product.Id == oi.ProductId));
}
[TestMethod]