using AyCode.Core.Interfaces; using AyCode.Entities; using AyCode.Services.Nav.Ekaer; 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 feladót és a saját céget egységesen -ként kezeli. /// A TradeType/TradeReasonType enumokat aliasszal hozzuk be a Models.TradeCardType osztály /// és a Models.Common.TradeCardType enum névütközése miatt. /// public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper { /// A NAV EKÁER magyar rendszer — a „belföld" mindig HU; minden más feladó-ország import. private const string HomeCountry = "HU"; public IReadOnlyList MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create) { ArgumentNullException.ThrowIfNull(shipping); ArgumentNullException.ThrowIfNull(company); 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, company), }); } return operations; } private static TradeCardType BuildTradeCard(Shipping shipping, ShippingDocument document, EkaerCompanyInfo company) { var seller = document.Partner; // a beszállító (feladó) — ICompanyInfoBase var tradeCard = new TradeCardType { TradeType = ResolveTradeType(seller), ModByCarrierEnabled = false, // mi jelentünk; a fuvarozó alapból nem módosíthat // Feladó / eladó = a beszállító SellerName = seller?.Name, SellerVatNumber = NormalizeVatNumber(seller?.TaxId), SellerCountry = NormalizeCountryCode(seller?.CountryCode, 2), SellerAddress = Truncate(seller?.FullAddress, 200), // Címzett = a bejelentő saját cége (bejövő relációban) DestinationName = company.Name, DestinationVatNumber = NormalizeVatNumber(company.TaxId), DestinationCountry = NormalizeCountryCode(company.CountryCode, 2), DestinationAddress = Truncate(company.FullAddress, 200), // Fuvarozó (Shipping.CargoPartner). Regisztrált EKAER-azonosító nincs, csak szöveges név. CarrierText = shipping.CargoPartner?.Name, // Lerakodás = saját telephely (a cégadatból); felrakodás = a beszállító telephelye. UnloadLocation = company.UnloadLocation, LoadLocation = BuildLoadLocation(seller), }; // Vonó jármű + vontatmány: az EKÁER külön bejegyzésként kéri (vehicle / vehicle2). if (shipping.CargoTruck != null) tradeCard.Vehicle = BuildVehicle(shipping.CargoTruck); if (shipping.CargoTrailer != null) tradeCard.Vehicle2 = BuildVehicle(shipping.CargoTrailer); foreach (var item in document.ShippingItems ?? []) tradeCard.Items.Add(BuildItem(item)); return tradeCard; } /// /// Belföldi feladó (HU) → D (belföld-belföld), egyébként → I (import). A NAV EKÁER magyar, /// így a belföld mindig HU; az export (E) jelenleg nincs leképezve (lásd EKAER_TODO #1, #7). /// private static TradeType ResolveTradeType(ICompanyInfoBase? seller) => string.Equals(seller?.CountryCode, HomeCountry, 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 (FBANKAPP-DMODEL-I-P6X4) 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, #10. }; private static BasicVehicleDetailType BuildVehicle(CargoTruck truck) => new() { PlateNumber = NormalizePlateNumber(truck.LicencePlate), Country = NormalizeCountryCode(truck.CountryCode, 3), }; /// /// Felrakodási hely a beszállító adataiból. (Magyar feladónál a NAV a Phone/Email-t is kéri, ami az /// entitásban nincs — lásd EKAER_TODO #6.) /// private static LocationType? BuildLoadLocation(ICompanyInfoBase? seller) { if (seller is 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, }; } /// 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 == '-')]); 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} — kötőjel/szóköz NEM engedett. 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 is null ? null : value.Length <= maxLen ? value : value[..maxLen]; }