Add FruitBank EKÁER mapping layer and validation docs
- Introduced IFruitBankEkaerService and implementation for mapping and submitting Shipping as EKÁER tradeCards - Added IShippingToEkaerMapper and ShippingToEkaerMapper for domain-to-NAV mapping with normalization logic - Created EkaerMappingOptions for config-driven mapping inputs - Added unit tests for mapping logic (ShippingToEkaerMapperTests) - Expanded EKAER_VALIDATION.md with C# validation implementation details - Updated README.md to clarify mapping/submission flow and project boundaries - Improved OrderClientTests assertions for nullability and clarity - Updated settings.local.json with new dev workflow commands
This commit is contained in:
parent
b541405640
commit
2fb1151e5d
|
|
@ -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\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <inheritdoc cref="IFruitBankEkaerService"/>
|
||||
/// <remarks>
|
||||
/// A teljes lánc: <c>map</c> (<see cref="IShippingToEkaerMapper"/>, FruitBank.Common) →
|
||||
/// <c>validate → send</c> (<see cref="IEkaerSubmitService"/>, AyCode.Services). A NAV-fiók
|
||||
/// hitelesítő adatait (<c>INavCredentials</c>) és a <see cref="EkaerMappingOptions"/>-t a DI szolgáltatja.
|
||||
/// </remarks>
|
||||
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<EkaerSubmitResult> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// A FruitBank szerver-oldali EKÁER-fogyasztója: egy <see cref="Shipping"/>-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 <c>AyCode.Services</c>-ben él.
|
||||
/// </summary>
|
||||
public interface IFruitBankEkaerService
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 <see cref="EkaerSubmitResult"/>.
|
||||
/// </summary>
|
||||
Task<EkaerSubmitResult> SubmitShippingAsync(Shipping shipping, OperationType operation = OperationType.Create, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using AyCode.Services.Nav.Ekaer.Models;
|
||||
|
||||
namespace FruitBank.Common.Services.Ekaer;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ShippingToEkaerMapper"/> konfiguráció-függő bemenetei, amelyek NEM a <c>Shipping</c>-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.
|
||||
/// </summary>
|
||||
/// <remarks>Ezek később a szerver-oldali plugin beállításaiból (settings) töltődnek — lásd a plugin <c>docs/EKAER/EKAER_TODO.md</c> #2.</remarks>
|
||||
public sealed class EkaerMappingOptions
|
||||
{
|
||||
/// <summary>A bejelentő (FruitBank) neve — a tradeCard <c>destinationName</c>-je bejövő relációban.</summary>
|
||||
public string? DestinationName { get; set; }
|
||||
|
||||
/// <summary>A bejelentő adószáma (<c>destinationVatNumber</c>). Pattern: <c>[0-9A-Z-]{1,15}</c>.</summary>
|
||||
public string? DestinationVatNumber { get; set; }
|
||||
|
||||
/// <summary>A bejelentő országkódja (2 betű). Alapértelmezés: <c>HU</c>.</summary>
|
||||
public string DestinationCountryCode { get; set; } = "HU";
|
||||
|
||||
/// <summary>A bejelentő címe egybeírva (<c>destinationAddress</c>, max 200).</summary>
|
||||
public string? DestinationAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="LocationType"/>-ként adjuk át (a plugin-konfigból összeállítva).
|
||||
/// </summary>
|
||||
public LocationType? UnloadLocation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A bejelentő saját országkódja a tradeType meghatározásához: ha a feladó (beszállító) országa
|
||||
/// ezzel egyezik → <c>D</c> (belföld-belföld), egyébként → <c>I</c> (import). Alapértelmezés: <c>HU</c>.
|
||||
/// </summary>
|
||||
public string HomeCountryCode { get; set; } = "HU";
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using AyCode.Services.Nav.Ekaer.Models;
|
||||
using FruitBank.Common.Entities;
|
||||
|
||||
namespace FruitBank.Common.Services.Ekaer;
|
||||
|
||||
/// <summary>
|
||||
/// FruitBank domain → NAV EKÁER tradeCard leképezés. Egy bejövő <see cref="Shipping"/>-ből
|
||||
/// EKÁER tradeCard műveleteket állít elő (dokumentumonként egyet).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A NAV-protokollt és az authentikációt NEM kezeli — az az <c>AyCode.Services.Nav</c> réteg
|
||||
/// (<c>NavReportServiceBase</c> / <c>EkaerManageService</c>) 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 <c>docs/EKAER/README.md</c> és <c>EKAER_TODO.md</c> fájljaiban.
|
||||
/// </remarks>
|
||||
public interface IShippingToEkaerMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Leképezi a <paramref name="shipping"/> minden <c>ShippingDocument</c>-jét egy-egy EKÁER tradeCard műveletre.
|
||||
/// </summary>
|
||||
/// <param name="shipping">A bejövő szállítmány. A fuvarozó/jármű a Shipping szintjén, az eladó/tételek a dokumentum szintjén élnek.</param>
|
||||
/// <param name="options">A konfiguráció-függő adatok (destination cég, lerakodási hely, home country).</param>
|
||||
/// <param name="operation">A tradeCard művelet típusa. Alapértelmezés: <see cref="OperationType.Create"/>.</param>
|
||||
/// <returns>Dokumentumonként egy <see cref="TradeCardOperationType"/>, a beérkezés sorrendjében indexelve.</returns>
|
||||
IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerMappingOptions options, OperationType operation = OperationType.Create);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <inheritdoc cref="IShippingToEkaerMapper"/>
|
||||
/// <remarks>
|
||||
/// Tiszta (állapotmentes) leképező. A <c>TradeType</c>/<c>TradeReasonType</c> enumokat aliasszal hozzuk be,
|
||||
/// hogy elkerüljük a <c>Models.Common.TradeCardType</c> enum és a <c>Models.TradeCardType</c> osztály névütközését.
|
||||
/// </remarks>
|
||||
public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper
|
||||
{
|
||||
public IReadOnlyList<TradeCardOperationType> MapShipping(Shipping shipping, EkaerMappingOptions options, OperationType operation = OperationType.Create)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(shipping);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
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++;
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bejövő (beszerzés) reláció: belföldi feladó → <c>D</c> (belföld-belföld), egyébként → <c>I</c> (import,
|
||||
/// közösségből belföldre). Az export (<c>E</c>) jelenleg nincs leképezve (lásd EKAER_TODO #1, #7).
|
||||
/// </summary>
|
||||
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),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 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 <c>Partner</c> entitásban nincs — lásd EKAER_TODO #6.)
|
||||
/// </summary>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Egybeírt cím (<c>sellerAddress</c>/<c>destinationAddress</c>, max 200): "1234 Budapest Fő utca 1.".</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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 == '-').ToArray());
|
||||
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> — a kötőjel/szóköz NEM engedett, ezért eltávolítjuk.</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);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tesztek a <see cref="ShippingToEkaerMapper"/>-re — a FruitBank <c>Shipping</c> → NAV EKÁER tradeCard
|
||||
/// leképezésre. Tisztán memóriában felépített entitásokon fut (nincs hálózat/DB), determinisztikus.
|
||||
/// </summary>
|
||||
[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<ArgumentNullException>(() => Mapper.MapShipping(null!, CreateOptions()));
|
||||
|
||||
[TestMethod]
|
||||
public void MapShipping_NullOptions_Throws()
|
||||
=> Assert.ThrowsExactly<ArgumentNullException>(() => Mapper.MapShipping(CreateShipping(), null!));
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue