268 lines
13 KiB
C#
268 lines
13 KiB
C#
using AyCode.Core.Interfaces;
|
|
using AyCode.Entities;
|
|
using AyCode.Services.Nav.Ekaer;
|
|
using AyCode.Services.Nav.Ekaer.Models;
|
|
using FruitBank.Common.Dtos;
|
|
using FruitBank.Common.Entities;
|
|
using Nop.Core.Domain.Customers;
|
|
using TradeReasonType = AyCode.Services.Nav.Ekaer.Models.Common.TradeReasonType;
|
|
using TradeType = AyCode.Services.Nav.Ekaer.Models.Common.TradeType;
|
|
|
|
namespace FruitBank.Common.Services.Ekaer;
|
|
|
|
/// <inheritdoc cref="IShippingToEkaerMapper"/>
|
|
/// <remarks>
|
|
/// Tiszta (állapotmentes) leképező. A feladót és a saját céget egységesen <see cref="ICompanyInfoBase"/>-ként kezeli.
|
|
/// A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be a <c>Models.TradeCardType</c> osztály
|
|
/// és a <c>Models.Common.TradeCardType</c> enum névütközése miatt.
|
|
/// </remarks>
|
|
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>
|
|
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;
|
|
/// bekötéskor innen jön). HUF → nincs átváltás (rate 1).</summary>
|
|
private const string OutboundCurrency = "HUF";
|
|
|
|
private readonly IEkaerSettings _settings;
|
|
|
|
public ShippingToEkaerMapper(IEkaerSettings settings)
|
|
=> _settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
|
|
|
public IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerCompanyInfo company, OperationType operation = OperationType.Create)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(shipping);
|
|
ArgumentNullException.ThrowIfNull(company);
|
|
|
|
var operations = new List<TradeCardOperationType>();
|
|
var index = 0;
|
|
|
|
// Granularitás: egy ShippingDocument → egy tradeCard (lásd EKAER_TODO #5).
|
|
foreach (var document in shipping.ShippingDocuments ?? [])
|
|
{
|
|
index++;
|
|
var rateToHuf = EkaerValueCalculator.ResolveRateToHuf(document.Partner?.Currency, _settings.EurHufRate);
|
|
operations.Add(new TradeCardOperationType
|
|
{
|
|
Index = index,
|
|
Operation = operation,
|
|
TradeCard = BuildTradeCard(shipping, document, company, rateToHuf),
|
|
});
|
|
}
|
|
|
|
return operations;
|
|
}
|
|
|
|
public TradeCardType MapDocument(ShippingDocument document, EkaerCompanyInfo company)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(document);
|
|
ArgumentNullException.ThrowIfNull(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)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(order);
|
|
ArgumentNullException.ThrowIfNull(company);
|
|
|
|
var customer = order.Customer;
|
|
var rateToHuf = EkaerValueCalculator.ResolveRateToHuf(OutboundCurrency, _settings.EurHufRate);
|
|
|
|
var tradeCard = new TradeCardType
|
|
{
|
|
// Kimenő belföldi értékesítés. (Export `E` + külföldi deviza: későbbi — jelenleg minden HUF/belföld.)
|
|
TradeType = TradeType.D,
|
|
ModByCarrierEnabled = false,
|
|
|
|
// Eladó = mi (kimenő relációban)
|
|
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,
|
|
UnloadLocation = BuildCustomerLocation(customer),
|
|
|
|
// Vonó jármű / fuvarozó: a kimenő fuvar-adat (OrderDto) bekötéséig nincs forrás — üresen marad,
|
|
// a validátor jelzi (mint a bejövőnél kezdetben). TODO: order.<transport> → Vehicle / CarrierText.
|
|
};
|
|
|
|
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)
|
|
{
|
|
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.Site,
|
|
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, rateToHuf));
|
|
return tradeCard;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Belföldi feladó (HU) → <c>D</c> (belföld-belföld), egyébként → <c>I</c> (import). A NAV EKÁER magyar,
|
|
/// így a belföld mindig HU; az export (<c>E</c>) jelenleg nincs leképezve (lásd EKAER_TODO #1, #7).
|
|
/// </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(),
|
|
// 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 = 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)
|
|
Value = EkaerValueCalculator.ItemValueHuf(item, rateToHuf), // beszerzési érték HUF-ban (Partner.Currency → árfolyam); 0/ismeretlen → null
|
|
};
|
|
|
|
/// <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>
|
|
private static TradeCardItemType BuildOutboundItem(OrderItemDto item, double rateToHuf) => new()
|
|
{
|
|
ItemExternalId = item.Id.ToString(),
|
|
// Kimenő áru = értékesítés → S. (Enum: S=értékesítés, A=beszerzés, W=bérmunka, O=egyéb.)
|
|
TradeReason = TradeReasonType.S,
|
|
ProductVtsz = NormalizeVtsz(item.ProductDto?.Gtin), // VTSZ — átmenetileg a Gtin oszlopban (FBANKAPP-DMODEL-I-P6X4)
|
|
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()
|
|
{
|
|
PlateNumber = NormalizePlateNumber(truck.LicencePlate),
|
|
Country = NormalizeCountryCode(truck.CountryCode, 3),
|
|
};
|
|
|
|
/// <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
|
|
/// entitásban nincs — lásd EKAER_TODO #6.)
|
|
/// </summary>
|
|
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,
|
|
};
|
|
}
|
|
|
|
/// <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());
|
|
}
|
|
|
|
/// <summary>Lerakodási hely a vevő adataiból (kimenő reláció). Ország jelenleg HU (belföld).</summary>
|
|
private static LocationType? BuildCustomerLocation(Customer? customer)
|
|
{
|
|
if (customer is null) return null;
|
|
return new LocationType
|
|
{
|
|
Name = customer.Company,
|
|
VatNumber = NormalizeVatNumber(customer.VatNumber),
|
|
Country = HomeCountry,
|
|
ZipCode = NormalizeZipCode(customer.ZipPostalCode),
|
|
City = customer.City,
|
|
Street = customer.StreetAddress,
|
|
};
|
|
}
|
|
|
|
/// <summary>Adószám normalizálása. Pattern: <c>[0-9A-Z-]{1,15}</c>.</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>Országkód normalizálása. Pattern: <c>[A-Z]{1,maxLen}</c> (seller/location: 2, jármű: 3).</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>Rendszám normalizálása. Pattern: <c>[A-Z0-9ÖŐÜŰ]{4,15}</c> — kötőjel/szóköz NEM engedett.</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>Irányítószám normalizálása. Pattern: <c>[A-Z0-9 -]{2,10}</c> vagy üres.</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>VTSZ normalizálása. Pattern: <c>[0-9]{4,8}</c> — a forrásban tagolt formátum is lehet
|
|
/// („0805 10 00", „0805.10.00"), ezért csak a számjegyek maradnak; a 4-nél rövidebbet a validátor jelzi.</summary>
|
|
private static string? NormalizeVtsz(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
var cleaned = new string([.. value.Where(char.IsAsciiDigit)]);
|
|
return Truncate(EmptyToNull(cleaned), 8);
|
|
}
|
|
|
|
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];
|
|
}
|