using Fake.ObjectStorage; using Oms.Application.Dtos.Orders; using Oms.Application.Storage; using Oms.Domain.ChannelsAggregate.Repositories; using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Entities; using Oms.Domain.SystemAggregate.Repositories; namespace Oms.Application.Shared; /// /// 从订单表及关联子表组装 itfx createOrderOne 标准请求体。 /// public static class OrderDownstreamCreateOneBodyBuilder { public static async Task BuildAsync( Order order, OrderSender? sender, OrderRecipient? recipient, IReadOnlyList declarations, IReadOnlyList weights, ICountryRepository countryRepository, IServiceChannelRepository serviceChannelRepository, IObjectStorage? fileStorage = null, CancellationToken cancellationToken = default) { var countryIds = new List(); if (sender?.Country is > 0) { countryIds.Add(sender.Country.Value); } if (recipient?.Country is > 0) { countryIds.Add(recipient.Country.Value); } var countries = countryIds.Count == 0 ? [] : await countryRepository.GetListAsync(x => countryIds.Contains(x.Id)); var idToCode = countries.ToDictionary(x => x.Id, x => x.CountryCode); var forecast = weights.FirstOrDefault(x => string.Equals(x.Type, OrderWeightTypeCodes.Forecast, StringComparison.OrdinalIgnoreCase)) ?? weights.FirstOrDefault(); var productCode = await ResolveForecastProductCodeAsync( order, serviceChannelRepository, cancellationToken); return new ItfxCreateOrderOneBody { ClientNo = order.ClientNo, TrackNo = order.TrackNo, ServiceNo = order.ServiceNo, ProductCode = productCode, CargoAttribute = order.CargoAttribute, WarehouseId = order.WarehouseId, CustomerId = order.CustomerId, Length = forecast?.Length, Width = forecast?.Width, Height = forecast?.Height, ActualWeight = forecast?.ActualWeight, VolumetricWeight = forecast?.VolumetricWeight, ChargeableWeight = forecast?.ChargeableWeight, Cubic = forecast?.Cubic, IsCod = order.IsCod > 0, CodCurrency = order.CodCurrency, CodSum = FormatDecimal(order.CodSum), DeclaredCurrency = order.DeclaredCurrency, DeclaredSum = FormatDecimal(order.DeclaredSum), Quantity = order.Quantity?.ToString(), Receiver = MapReceiver(recipient, order.ConsigneeCountry, idToCode), Sender = MapSender(sender, idToCode), Items = declarations.Select(x => MapItem(x, fileStorage)).ToList() }; } /// /// createOrderOne.productCode = 渠道「预报渠道 code」: /// 优先订单固化的 ChannelForecastCode,否则 live 查 t_service_channel.ForecastCode。 /// private static async Task ResolveForecastProductCodeAsync( Order order, IServiceChannelRepository serviceChannelRepository, CancellationToken cancellationToken) { var snapshotted = order.ChannelForecastCode?.Trim(); if (!string.IsNullOrWhiteSpace(snapshotted)) { return snapshotted; } var channelCode = order.ChannelCode?.Trim(); if (string.IsNullOrWhiteSpace(channelCode)) { return null; } var channel = await serviceChannelRepository.GetByChannelCodeAsync(channelCode, cancellationToken); var forecastCode = channel?.ForecastCode?.Trim(); return string.IsNullOrWhiteSpace(forecastCode) ? null : forecastCode; } private static string? FormatDecimal(decimal? value) => value.HasValue ? value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture) : null; private static string? ResolveCountryIso(int? countryId, string? orderConsigneeCountry, IReadOnlyDictionary idToCode) { if (!string.IsNullOrWhiteSpace(orderConsigneeCountry)) { var trimmed = orderConsigneeCountry.Trim(); if (trimmed.Length == 2) { return trimmed.ToUpperInvariant(); } } if (countryId is > 0 && idToCode.TryGetValue(countryId.Value, out var iso) && !string.IsNullOrWhiteSpace(iso)) { return iso.Trim().ToUpperInvariant(); } return null; } private static OrderParcelReceiverInput? MapReceiver( OrderRecipient? recipient, string? orderConsigneeCountry, IReadOnlyDictionary idToCode) { if (recipient == null) { return string.IsNullOrWhiteSpace(orderConsigneeCountry) ? null : new OrderParcelReceiverInput { CountryCode = orderConsigneeCountry.Trim().ToUpperInvariant() }; } return new OrderParcelReceiverInput { RecipientsName = recipient.RecipientsName, CompanyName = recipient.CompanyName, Phone = recipient.Phone, TelePhone = recipient.TelePhone, Email = recipient.Email, CountryCode = ResolveCountryIso(recipient.Country, orderConsigneeCountry, idToCode), Province = recipient.Province, City = recipient.City, StreetNumber = recipient.StreetNumber, StreetAddress1 = recipient.StreetAddress1, StreetAddress2 = recipient.StreetAddress2, StreetAddress3 = recipient.StreetAddress3, ZipCode = recipient.ZipCode, TaxType = recipient.TaxType, TaxNo = recipient.TaxNo, CertificateType = recipient.CertificateType, CertificateNo = recipient.CertificateNo, Eori = recipient.Eori, Vat = recipient.Vat }; } private static OrderParcelSenderInput? MapSender(OrderSender? sender, IReadOnlyDictionary idToCode) { if (sender == null) { return null; } return new OrderParcelSenderInput { SenderName = sender.SenderName, Company = sender.Company, Phone = sender.Phone, TelePhone = sender.TelePhone, Email = sender.Email, CountryCode = ResolveCountryIso(sender.Country, null, idToCode), Province = sender.Province, City = sender.City, ZipCode = sender.ZipCode, StreetAddress1 = sender.StreetAddress1, StreetAddress2 = sender.StreetAddress2, StreetAddress3 = sender.StreetAddress3, HouseNumber = sender.HouseNumber }; } private static OrderParcelItemInput MapItem(OrderDeclaration x, IObjectStorage? fileStorage) => new() { CnName = x.CnName, EnName = x.EnName, Sku = x.Sku, Unit = x.Unit, UnitPrice = x.UnitPrice, UnitWeight = x.UnitWeight, Quantity = x.Quantity, Amount = x.Amount, Weight = x.Weight, ImHsCode = x.ImHsCode, ExHsCode = x.ExHsCode ?? x.HsCode, DeclareElement = x.DeclareElement, // 始终赋值;空白→null。配合 OrderParcelItemInput 上 JsonIgnore(Never),请求体固定出现 "xxx": null。 ProductId = NullIfWhiteSpace(x.ProductId), NonStdProductId = NullIfWhiteSpace(x.NonStdProductId), StdProductId = NullIfWhiteSpace(x.StdProductId), SkuImage = fileStorage?.GetPublicUrl(x.SkuImage) ?? x.SkuImage, Category = x.Category, Brand = x.Brand, BrandType = x.BrandType, IsMagnetic = x.IsMagnetic > 0, Distribution = x.Distribution, Material = x.Material, EnMaterial = x.EnMaterial, Purpose = x.Purpose, Attributes = x.Attributes, OriginCountry = x.OriginCountry, SalesLink = x.SalesLink, Model = x.Model, Specifications = x.Specifications, Comment = x.Comment }; private static string? NullIfWhiteSpace(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); }