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();
}