diff --git a/FruitBank.Common/Dtos/OrderDto.cs b/FruitBank.Common/Dtos/OrderDto.cs index e2fe48bb..40cdd8a8 100644 --- a/FruitBank.Common/Dtos/OrderDto.cs +++ b/FruitBank.Common/Dtos/OrderDto.cs @@ -76,6 +76,22 @@ public class OrderDto : MgOrderDto, IOrderDto [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrNull('DateOfReceipt')")] public DateTime? DateOfReceipt => GenericAttributes.GetValueOrNull(nameof(IOrderDto.DateOfReceipt)); + [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => GenericAttributes single 'LicensePlate' value (raw, NAV mapper normalizes)")] + public string? LicensePlate => + GenericAttributes.SingleOrDefault(x => x.Key == nameof(IOrderDto.LicensePlate))?.Value; + + [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ToonDescription(BusinessRule = "get => deserialize GenericAttributes single 'Site' JSON value to OrderSiteSnapshot")] + public OrderSiteSnapshot? Site + { + get + { + var json = GenericAttributes.SingleOrDefault(x => x.Key == nameof(IOrderDto.Site))?.Value; + return string.IsNullOrWhiteSpace(json) ? null : JsonConvert.DeserializeObject(json); + } + } + [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] [ToonDescription(BusinessRule = "get => GenericAttributes.GetValueOrDefault('RevisorId', 0)")] public int RevisorId => GenericAttributes.GetValueOrDefault(nameof(IOrderDto.RevisorId), 0); @@ -87,7 +103,7 @@ public class OrderDto : MgOrderDto, IOrderDto [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] [ToonDescription(BusinessRule = "get => OrderItemDtos.Count > 0 && OrderItemDtos.All(oi => oi.IsAudited)")] public bool IsAllOrderItemAudited => OrderItemDtos.Count > 0 && OrderItemDtos.All(oi => oi.IsAudited); - + [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore] [ToonDescription(BusinessRule = "get => OrderItemDtos.All(oi => oi.AverageWeightIsValid)")] public bool IsAllOrderItemAvgWeightValid => OrderItemDtos.All(oi => oi.AverageWeightIsValid); @@ -106,7 +122,7 @@ public class OrderDto : MgOrderDto, IOrderDto //if (GenericAttributes.TryGetValue(nameof(IOrderDto.DateOfReceipt), out var value)) return value; //var dateOfReceipt = GenericAttributes.SingleOrDefault(x => x.Key == nameof(IOrderDto.DateOfReceipt))?.Value ?? string.Empty; //return dateOfReceipt.IsNullOrWhiteSpace() ? null : CommonHelper.To(dateOfReceipt); - public OrderDto() :base() + public OrderDto() : base() { } public OrderDto(int orderId) : base(orderId) { } diff --git a/FruitBank.Common/Entities/OrderSiteSnapshot.cs b/FruitBank.Common/Entities/OrderSiteSnapshot.cs new file mode 100644 index 00000000..053832aa --- /dev/null +++ b/FruitBank.Common/Entities/OrderSiteSnapshot.cs @@ -0,0 +1,22 @@ +namespace FruitBank.Common.Dtos; + +/// +/// Frozen snapshot of the customer site (telephely) selected for an order, stored +/// as JSON in the order's "Site" generic attribute. Shaped to the NAV EKÁER +/// LocationType (UnloadLocation) so the outgoing trade card can use it directly. +/// +public class OrderSiteSnapshot +{ + /// The customer Address this snapshot was taken from (reference only; data is frozen). + public int SourceAddressId { get; set; } + + public string? Name { get; set; } // LocationType.Name (Company or person name) + public string? VatNumber { get; set; } // LocationType.VatNumber (customer's VAT) + public string? CountryCode { get; set; } // LocationType.Country (ISO, e.g. "HU") + public string? ZipCode { get; set; } // LocationType.ZipCode + public string? City { get; set; } // LocationType.City + public string? Street { get; set; } // LocationType.Street (Address1 [+ Address2]) + + /// Pre-formatted one-line display for UI. + public string? Display { get; set; } +} \ No newline at end of file diff --git a/FruitBank.Common/Interfaces/IOrderDto.cs b/FruitBank.Common/Interfaces/IOrderDto.cs index c3a608d1..d1dcafb9 100644 --- a/FruitBank.Common/Interfaces/IOrderDto.cs +++ b/FruitBank.Common/Interfaces/IOrderDto.cs @@ -16,6 +16,9 @@ public interface IOrderDto : IMgOrderDto, IMeasured, I int RevisorId { get; } int MeasurementOwnerId { get; } + string? LicensePlate { get; } + OrderSiteSnapshot? Site { get; } + bool IsComplete { get; } bool HasMeasuringAccess(int? customerId, bool isRevisorUser = false); diff --git a/FruitBank.Common/Loggers/README.md b/FruitBank.Common/Loggers/README.md new file mode 100644 index 00000000..b921ec05 --- /dev/null +++ b/FruitBank.Common/Loggers/README.md @@ -0,0 +1,7 @@ +# Loggers + +SignalR client-to-server log writer. + +## Key Files + +- **`SignaRClientLogItemWriter.cs`** — Routes client logs to `{BaseUrl}/loggerHub` via SignalR. Configurable by AppType and LogLevel. diff --git a/FruitBank.Common/Loggers/SignaRClientLogItemWriter.cs b/FruitBank.Common/Loggers/SignaRClientLogItemWriter.cs new file mode 100644 index 00000000..79e9859a --- /dev/null +++ b/FruitBank.Common/Loggers/SignaRClientLogItemWriter.cs @@ -0,0 +1,17 @@ +using AyCode.Core.Enums; +using AyCode.Core.Loggers; +using AyCode.Services.Loggers; + +namespace FruitBank.Common.Loggers +{ + public class SignaRClientLogItemWriter : AcSignaRClientLogItemWriter + { + public SignaRClientLogItemWriter() : this(AppType.Web, LogLevel.Detail, null) + { + } + public SignaRClientLogItemWriter(AppType appType, LogLevel logLevel, string? categoryName = null) : base($"{FruitBankConstClient.BaseUrl}/{FruitBankConstClient.LoggerHubName}", appType, logLevel, categoryName) + { + } + } + +} diff --git a/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs b/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs index 44b6d51b..2dee6d65 100644 --- a/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs +++ b/FruitBank.Common/Services/Ekaer/ShippingToEkaerMapper.cs @@ -145,13 +145,14 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper Seller = CompanyEndpoint(company), Buyer = CustomerEndpoint(customer), LoadLocation = company.Site, - UnloadLocation = BuildCustomerLocation(customer), + // A lerakodási hely az orderre választott telephely (snapshot); ha nincs beállítva, + // fallback a vevő fő címére (a korábbi viselkedés). + UnloadLocation = BuildSiteLocation(order.Site) ?? BuildCustomerLocation(customer), Lines = lines, - // Kimenőnél NEM töltjük a szállítmányozót: a vevő maga viszi el, és a vevő MÁR a címzett (destinationName) — - // külön fuvarozót nem tartunk nyilván; a carrierText opcionális (a NAV nem követeli) → üresen hagyjuk. + // Kimenőnél nincs külön szállítmányozó: a vevő maga viszi el → carrierText üres (a NAV nem követeli). CarrierName = null, - // A vonó jármű (rendszám) a customer-hez még nincs bekötve → üresen marad, a felrakodás megkezdéséig pótolandó - // (a validátor warningolja). Amint bekötik, a Vehicle is innen jön. + // A vonó jármű rendszáma az orderre mentett LicensePlate-ből (a customer adja meg, ill. az auto-hozzárendelés). + Vehicle = BuildVehicleFromPlate(order.LicensePlate), }; } @@ -279,6 +280,37 @@ public sealed class ShippingToEkaerMapper : IShippingToEkaerMapper }; } + + /// Lerakodási hely az orderre választott telephely-snapshotból (kimenő). A snapshot már a rendelés + /// pillanatában befagyott LocationType-mezőket hordoz (név, adószám, irsz., város, utca, ország). + private static LocationType? BuildSiteLocation(OrderSiteSnapshot? site) + { + if (site is null) return null; + return new LocationType + { + Name = site.Name, + VatNumber = NormalizeVatNumber(site.VatNumber), + Country = NormalizeCountryCode(site.CountryCode, 2), + ZipCode = NormalizeZipCode(site.ZipCode), + City = site.City, + Street = site.Street, + }; + } + + /// A vonó jármű az orderre mentett rendszámból (kimenő). Ország jelenleg belföld (HU); a rendszámot + /// a meglévő NAV-pattern szerint normalizáljuk. Üres/érvénytelen rendszám → nincs jármű (a validátor jelzi). + private static BasicVehicleDetailType? BuildVehicleFromPlate(string? plateNumber) + { + var plate = NormalizePlateNumber(plateNumber); + if (plate is null) return null; + return new BasicVehicleDetailType + { + PlateNumber = plate, + Country = NormalizeCountryCode(HomeCountry, 3), + }; + } + + /// A számla-pénznem → HUF szorzó, NEM dobó változat (a kapu külföldinél küszöb nélkül jelent, ott az érték /// nem kell): HUF → 1; külföldi + érvényes árfolyam → árfolyam; külföldi + hiányzó árfolyam → 0 (a tétel-érték null lesz). /// Generáláskor a service config-kapuja (TryConfigError) előbb elvágja a hiányzó-árfolyamos külföldi esetet. diff --git a/FruitBankHybrid.Web.Client/FruitBankHybrid.Web.Client.csproj b/FruitBankHybrid.Web.Client/FruitBankHybrid.Web.Client.csproj index 62501ae0..ed55edfc 100644 --- a/FruitBankHybrid.Web.Client/FruitBankHybrid.Web.Client.csproj +++ b/FruitBankHybrid.Web.Client/FruitBankHybrid.Web.Client.csproj @@ -7,7 +7,7 @@ true Default - true + false true true